aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhazel <hazel@hazelthats.me>2026-01-26 22:04:39 +0100
committerhazel <hazel@hazelthats.me>2026-01-26 22:04:39 +0100
commit567c422f8cd42eba2437f9a8c2522716a1649be7 (patch)
tree93c5b296f3b7c14b626d0aadf5cad37764c41c74
downloadcelesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.tar.gz
celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.tar.bz2
celesteia-567c422f8cd42eba2437f9a8c2522716a1649be7.zip
celesteia archive, last updated april 9th 2024
Signed-off-by: hazel <hazel@hazelthats.me>
-rw-r--r--.config/dotnet-tools.json36
-rw-r--r--.github/workflows/dotnet.yml22
-rw-r--r--.gitignore59
-rw-r--r--.mgstats1
-rw-r--r--.vscode/launch.json58
-rw-r--r--.vscode/tasks.json71
-rw-r--r--Celesteia.csproj46
-rw-r--r--Content/Content.mgcb208
-rw-r--r--Content/Hobo.spritefont60
-rw-r--r--Content/branding/leafal/leafal-text-logo.pngbin0 -> 22619 bytes
-rw-r--r--Content/branding/leafal/splash.mp3bin0 -> 73828 bytes
-rw-r--r--Content/branding/leafal/splash.mp3.sfkbin0 -> 4528 bytes
-rw-r--r--Content/celesteia/title-rebrand-borders.pngbin0 -> 1160 bytes
-rw-r--r--Content/music/Landing Light.oggbin0 -> 4583221 bytes
-rw-r--r--Content/music/Stargaze Symphony.oggbin0 -> 4095788 bytes
-rw-r--r--Content/sprites/blockbreak.pngbin0 -> 193 bytes
-rw-r--r--Content/sprites/blocks.pngbin0 -> 2845 bytes
-rw-r--r--Content/sprites/blockselection.pngbin0 -> 139 bytes
-rw-r--r--Content/sprites/crashed_capsule.pngbin0 -> 498 bytes
-rw-r--r--Content/sprites/entities/player/astronaut.pngbin0 -> 385 bytes
-rw-r--r--Content/sprites/items.pngbin0 -> 3536 bytes
-rw-r--r--Content/sprites/skybox/nebula.pngbin0 -> 191878 bytes
-rw-r--r--Content/sprites/skybox/shadow.pngbin0 -> 535592 bytes
-rw-r--r--Content/sprites/skybox/stars.pngbin0 -> 7772 bytes
-rw-r--r--Content/sprites/skybox/stars2.pngbin0 -> 19140 bytes
-rw-r--r--Content/sprites/ui.pngbin0 -> 1828 bytes
-rw-r--r--Content/sprites/ui/button.pngbin0 -> 234 bytes
-rw-r--r--Content/sprites/ui/window.pngbin0 -> 217 bytes
-rw-r--r--Content/ttf/hobo.ttfbin0 -> 62116 bytes
-rw-r--r--GameInstance.cs151
-rw-r--r--Icon.bmpbin0 -> 16534 bytes
-rw-r--r--Icon.icobin0 -> 142862 bytes
-rw-r--r--LICENSE21
-rw-r--r--Program.cs14
-rw-r--r--README.md8
-rw-r--r--app.manifest43
-rw-r--r--img/background.jpgbin0 -> 49782 bytes
-rw-r--r--img/banner.jpgbin0 -> 34286 bytes
-rw-r--r--img/thumbnail.jpgbin0 -> 45636 bytes
-rw-r--r--source/game/WorldManager.cs33
-rw-r--r--source/game/components/CameraFollow.cs5
-rw-r--r--source/game/components/EntityAttributes.cs35
-rw-r--r--source/game/components/TargetPosition.cs7
-rw-r--r--source/game/components/entity/GameWorldEntity.cs14
-rw-r--r--source/game/components/items/Inventory.cs96
-rw-r--r--source/game/components/items/ItemStack.cs34
-rw-r--r--source/game/components/physics/CollisionBox.cs36
-rw-r--r--source/game/components/physics/PhysicsEntity.cs38
-rw-r--r--source/game/components/player/LocalPlayer.cs5
-rw-r--r--source/game/components/player/PlayerInput.cs14
-rw-r--r--source/game/components/skybox/SkyboxRotateZ.cs17
-rw-r--r--source/game/ecs/EntityFactory.cs111
-rw-r--r--source/game/ecs/GameWorld.cs41
-rw-r--r--source/game/input/GamepadHelper.cs59
-rw-r--r--source/game/input/InputManager.cs33
-rw-r--r--source/game/input/KeyboardHelper.cs53
-rw-r--r--source/game/input/MouseHelper.cs37
-rw-r--r--source/game/input/conditions/AllCondition.cs15
-rw-r--r--source/game/input/conditions/AnyCondition.cs15
-rw-r--r--source/game/input/conditions/AverageCondition.cs16
-rw-r--r--source/game/input/conditions/ICondition.cs5
-rw-r--r--source/game/input/definitions/InputDefinition.cs16
-rw-r--r--source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs10
-rw-r--r--source/game/input/definitions/gamepad/SensorGamepadDefinition.cs13
-rw-r--r--source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs11
-rw-r--r--source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs18
-rw-r--r--source/game/input/definitions/mouse/BinaryMouseDefinition.cs10
-rw-r--r--source/game/items/BlockItemActions.cs73
-rw-r--r--source/game/items/CooldownItemActions.cs17
-rw-r--r--source/game/items/FoliageItemActions.cs18
-rw-r--r--source/game/items/IItemActions.cs11
-rw-r--r--source/game/items/PickaxeItemActions.cs63
-rw-r--r--source/game/items/TorchItemActions.cs15
-rw-r--r--source/game/items/UpgradeItemActions.cs48
-rw-r--r--source/game/music/MusicManager.cs54
-rw-r--r--source/game/planets/BlockState.cs43
-rw-r--r--source/game/planets/Chunk.cs158
-rw-r--r--source/game/planets/ChunkMap.cs99
-rw-r--r--source/game/planets/GeneratedPlanet.cs35
-rw-r--r--source/game/planets/generation/IWorldGenerator.cs18
-rw-r--r--source/game/planets/generation/TerranPlanetGenerator.cs275
-rw-r--r--source/game/systems/CameraRenderSystem.cs43
-rw-r--r--source/game/systems/CameraSystem.cs39
-rw-r--r--source/game/systems/ChunkMapRenderSystem.cs63
-rw-r--r--source/game/systems/EntityDebugSystem.cs44
-rw-r--r--source/game/systems/LightingSystem.cs100
-rw-r--r--source/game/systems/LocalPlayerSystem.cs246
-rw-r--r--source/game/systems/TargetPositionSystem.cs37
-rw-r--r--source/game/systems/mainmenu/MainMenuBackgroundSystem.cs33
-rw-r--r--source/game/systems/mainmenu/MainMenuRenderSystem.cs43
-rw-r--r--source/game/systems/physics/PhysicsCollisionDebugSystem.cs61
-rw-r--r--source/game/systems/physics/PhysicsSystem.cs42
-rw-r--r--source/game/systems/physics/PhysicsWorldCollisionSystem.cs83
-rw-r--r--source/game/systems/ui/GameGUIDrawSystem.cs12
-rw-r--r--source/graphics/Camera2D.cs63
-rw-r--r--source/graphics/GraphicsManager.cs65
-rw-r--r--source/graphics/lighting/LightMap.cs139
-rw-r--r--source/lib/FastNoiseLite.cs2506
-rw-r--r--source/resources/Resources.cs81
-rw-r--r--source/resources/collections/BaseCollection.cs207
-rw-r--r--source/resources/management/BlockManager.cs77
-rw-r--r--source/resources/management/EntityManager.cs46
-rw-r--r--source/resources/management/FontManager.cs43
-rw-r--r--source/resources/management/ItemManager.cs47
-rw-r--r--source/resources/management/RecipeManager.cs34
-rw-r--r--source/resources/management/SkyboxAssets.cs42
-rw-r--r--source/resources/sprites/BlockFrames.cs53
-rw-r--r--source/resources/sprites/EntityFrames.cs32
-rw-r--r--source/resources/sprites/SkyboxPortionFrames.cs51
-rw-r--r--source/resources/types/BlockType.cs71
-rw-r--r--source/resources/types/EntityType.cs20
-rw-r--r--source/resources/types/ItemType.cs21
-rw-r--r--source/resources/types/Recipe.cs48
-rw-r--r--source/resources/types/TileEntityType.cs27
-rw-r--r--source/resources/types/builders/BlockTypeBuilder.cs82
-rw-r--r--source/resources/types/builders/ItemTypeBuilder.cs67
-rw-r--r--source/screens/GameplayScreen.cs81
-rw-r--r--source/screens/MainMenuScreen.cs85
-rw-r--r--source/screens/SplashScreen.cs100
-rw-r--r--source/screens/TextTestScreen.cs73
-rw-r--r--source/ui/Rect.cs188
-rw-r--r--source/ui/TextAlignment.cs10
-rw-r--r--source/ui/UI.cs81
-rw-r--r--source/ui/elements/Button.cs173
-rw-r--r--source/ui/elements/Clickable.cs21
-rw-r--r--source/ui/elements/Container.cs91
-rw-r--r--source/ui/elements/Element.cs53
-rw-r--r--source/ui/elements/IClickable.cs9
-rw-r--r--source/ui/elements/IContainer.cs12
-rw-r--r--source/ui/elements/IElement.cs52
-rw-r--r--source/ui/elements/Image.cs59
-rw-r--r--source/ui/elements/Label.cs62
-rw-r--r--source/ui/elements/game/CraftingRecipeSlot.cs173
-rw-r--r--source/ui/elements/game/CraftingWindow.cs62
-rw-r--r--source/ui/elements/game/InventorySlot.cs191
-rw-r--r--source/ui/elements/game/InventoryWindow.cs56
-rw-r--r--source/ui/elements/game/PauseMenu.cs74
-rw-r--r--source/ui/elements/game/controls/ControlTips.cs51
-rw-r--r--source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs97
-rw-r--r--source/ui/elements/game/tooltips/ItemDisplay.cs21
-rw-r--r--source/ui/elements/game/tooltips/ItemTooltipDisplay.cs62
-rw-r--r--source/ui/elements/game/tooltips/TooltipDisplay.cs8
-rw-r--r--source/ui/guis/DebugGUI.cs66
-rw-r--r--source/ui/guis/GUI.cs48
-rw-r--r--source/ui/guis/MainMenu.cs225
-rw-r--r--source/ui/guis/game/GameGUI.cs349
-rw-r--r--source/ui/properties/TextProperties.cs62
147 files changed, 9885 insertions, 0 deletions
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..883648b
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,36 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-mgcb": {
+ "version": "3.8.1.263",
+ "commands": [
+ "mgcb"
+ ]
+ },
+ "dotnet-mgcb-editor": {
+ "version": "3.8.1.263",
+ "commands": [
+ "mgcb-editor"
+ ]
+ },
+ "dotnet-mgcb-editor-linux": {
+ "version": "3.8.1.263",
+ "commands": [
+ "mgcb-editor-linux"
+ ]
+ },
+ "dotnet-mgcb-editor-windows": {
+ "version": "3.8.1.263",
+ "commands": [
+ "mgcb-editor-windows"
+ ]
+ },
+ "dotnet-mgcb-editor-mac": {
+ "version": "3.8.1.263",
+ "commands": [
+ "mgcb-editor-mac"
+ ]
+ }
+ }
+} \ No newline at end of file
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 0000000..f8efa97
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,22 @@
+name: .NET Build
+
+on: [push]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+ - name: Restore, Build Celesteia
+ run: dotnet build
+ - name: Publish Windows
+ run: dotnet publish -c Release -r win-x64 --self-contained
+ - name: Publish OSX
+ run: dotnet publish -c Release -r osx-x64 --self-contained
+ - name: Publish Linux
+ run: dotnet publish -c Release -r linux-x64 --self-contained \ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fefd893
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,59 @@
+.env
+
+#OS junk files
+[Tt]humbs.db
+*.DS_Store
+
+#Visual Studio files
+*.pidb
+*.userprefs
+*.[Oo]bj
+*.exe
+*.pdb
+*.user
+*.aps
+*.pch
+*.vspscc
+*.vssscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.[Cc]ache
+*.ilk
+*.log
+*.lib
+*.sbr
+*.sdf
+ipch/
+obj/
+[Bb]in
+[Dd]ebug*/
+[Rr]elease*/
+Ankh.NoLoad
+
+#Tooling
+_ReSharper*/
+*.resharper
+[Tt]est[Rr]esult*
+
+#Project files
+[Bb]uild/
+
+#Subversion files
+.svn
+
+# Office Temp Files
+~$*
+
+#monodroid private beta
+monodroid*.msi
+/CompiledContent/Android/IgnoreMe.dll
+/CompiledContent/OSX/IgnoreMe.dll
+/CompiledContent/Windows8/IgnoreMe.dll
+/CompiledContent/Windows/IgnoreMe.dll
+*.cachefile
+*.dll \ No newline at end of file
diff --git a/.mgstats b/.mgstats
new file mode 100644
index 0000000..eab26b3
--- /dev/null
+++ b/.mgstats
@@ -0,0 +1 @@
+Source File,Dest File,Processor Type,Content Type,Source File Size,Dest File Size,Build Seconds
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..61201bb
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,58 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "name": "Debug Gameplay",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/bin/Debug/net6.0/Celesteia.dll",
+ "args": ["-gameplayDebug"],
+ "cwd": "${workspaceFolder}",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "name": "Debug Text",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/bin/Debug/net6.0/Celesteia.dll",
+ "args": ["-textDebug"],
+ "cwd": "${workspaceFolder}",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "name": "Debug Standard",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/bin/Debug/net6.0/Celesteia.dll",
+ "args": [""],
+ "cwd": "${workspaceFolder}",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+} \ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..7fee7e0
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,71 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/Celesteia.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Publish Windows",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "-c",
+ "Release",
+ "-r",
+ "win-x64",
+ "--self-contained"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Publish OSX",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "-c",
+ "Release",
+ "-r",
+ "osx-x64",
+ "--self-contained"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Publish Linux",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "-c",
+ "Release",
+ "-r",
+ "linux-x64",
+ "--self-contained"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/Celesteia.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+} \ No newline at end of file
diff --git a/Celesteia.csproj b/Celesteia.csproj
new file mode 100644
index 0000000..1cca541
--- /dev/null
+++ b/Celesteia.csproj
@@ -0,0 +1,46 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net6.0</TargetFramework>
+ <RollForward>Major</RollForward>
+ <PublishReadyToRun>false</PublishReadyToRun>
+ <TieredCompilation>false</TieredCompilation>
+ </PropertyGroup>
+ <PropertyGroup>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
+ <ApplicationIcon>Icon.ico</ApplicationIcon>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)'=='Release'">
+ <DebugType>none</DebugType>
+ <DebugSymbols>false</DebugSymbols>
+ <PublishSingleFile>true</PublishSingleFile>
+ </PropertyGroup>
+ <ItemGroup>
+ <None Remove="Icon.ico" />
+ <None Remove="Icon.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Icon.ico" />
+ <EmbeddedResource Include="Icon.bmp" />
+ </ItemGroup>
+ <ItemGroup>
+ <MonoGameContentReference Include="Content\Content.mgcb" />
+ </ItemGroup>
+ <ItemGroup>
+ <TrimmerRootAssembly Include="Microsoft.Xna.Framework.Content.ContentTypeReader" Visible="false" />
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="MonoGame.Extended" Version="3.8.0" />
+ <PackageReference Include="MonoGame.Extended.Entities" Version="3.8.0" />
+ <PackageReference Include="MonoGame.Extended.Input" Version="3.8.0" />
+ <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
+ <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
+ </ItemGroup>
+ <Target Name="RestoreDotnetTools" BeforeTargets="Restore">
+ <Message Text="Restoring dotnet tools" Importance="High" />
+ <Exec Command="dotnet tool restore" />
+ </Target>
+ <ItemGroup>
+ <RuntimeHostConfigurationOption Include="System.Globalization.Invariant" Value="true" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Content/Content.mgcb b/Content/Content.mgcb
new file mode 100644
index 0000000..2f806da
--- /dev/null
+++ b/Content/Content.mgcb
@@ -0,0 +1,208 @@
+
+#----------------------------- Global Properties ----------------------------#
+
+/outputDir:bin/$(Platform)
+/intermediateDir:obj/$(Platform)
+/platform:DesktopGL
+/config:
+/profile:Reach
+/compress:False
+
+#-------------------------------- References --------------------------------#
+
+
+#---------------------------------- Content ---------------------------------#
+
+#begin branding/leafal/leafal-text-logo.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:branding/leafal/leafal-text-logo.png;branding/leafal/leafal_text_logo.png
+
+#begin branding/leafal/splash.mp3
+/importer:Mp3Importer
+/processor:SoundEffectProcessor
+/processorParam:Quality=Medium
+/build:branding/leafal/splash.mp3
+
+#begin celesteia/title-rebrand-borders.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:celesteia/title-rebrand-borders.png;celesteia/logo.png
+
+#begin Hobo.spritefont
+/importer:FontDescriptionImporter
+/processor:FontDescriptionProcessor
+/processorParam:PremultiplyAlpha=False
+/processorParam:TextureFormat=Compressed
+/build:Hobo.spritefont
+
+#begin music/Stargaze Symphony.ogg
+/importer:OggImporter
+/processor:SongProcessor
+/processorParam:Quality=Best
+/build:music/Stargaze Symphony.ogg;music/stargaze_symphony.ogg
+
+#begin music/Landing Light.ogg
+/importer:OggImporter
+/processor:SongProcessor
+/processorParam:Quality=Best
+/build:music/Landing Light.ogg;music/landing_light.ogg
+
+#begin sprites/blockbreak.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=True
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/blockbreak.png
+
+#begin sprites/blocks.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/blocks.png
+
+#begin sprites/blockselection.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=True
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/blockselection.png
+
+#begin sprites/crashed_capsule.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/crashed_capsule.png
+
+#begin sprites/entities/player/astronaut.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/entities/player/astronaut.png
+
+#begin sprites/items.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=True
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/items.png
+
+#begin sprites/skybox/nebula.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/skybox/nebula.png
+
+#begin sprites/skybox/shadow.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/skybox/shadow.png
+
+#begin sprites/skybox/stars.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/skybox/stars.png
+
+#begin sprites/skybox/stars2.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=False
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/skybox/stars2.png
+
+#begin sprites/ui/button.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=True
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/ui/button.png
+
+#begin sprites/ui/window.png
+/importer:TextureImporter
+/processor:TextureProcessor
+/processorParam:ColorKeyColor=255,0,255,255
+/processorParam:ColorKeyEnabled=True
+/processorParam:GenerateMipmaps=False
+/processorParam:PremultiplyAlpha=True
+/processorParam:ResizeToPowerOfTwo=False
+/processorParam:MakeSquare=False
+/processorParam:TextureFormat=Color
+/build:sprites/ui/window.png
+
diff --git a/Content/Hobo.spritefont b/Content/Hobo.spritefont
new file mode 100644
index 0000000..0072fc1
--- /dev/null
+++ b/Content/Hobo.spritefont
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+This file contains an xml description of a font, and will be read by the XNA
+Framework Content Pipeline. Follow the comments to customize the appearance
+of the font in your game, and to change the characters which are available to draw
+with.
+-->
+<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
+ <Asset Type="Graphics:FontDescription">
+
+ <!--
+ Modify this string to change the font that will be imported.
+ -->
+ <FontName>ttf/hobo.ttf</FontName>
+
+ <!--
+ Size is a float value, measured in points. Modify this value to change
+ the size of the font.
+ -->
+ <Size>12</Size>
+
+ <!--
+ Spacing is a float value, measured in pixels. Modify this value to change
+ the amount of spacing in between characters.
+ -->
+ <Spacing>0</Spacing>
+
+ <!--
+ UseKerning controls the layout of the font. If this value is true, kerning information
+ will be used when placing characters.
+ -->
+ <UseKerning>true</UseKerning>
+
+ <!--
+ Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
+ and "Bold, Italic", and are case sensitive.
+ -->
+ <Style>Regular</Style>
+
+ <!--
+ If you uncomment this line, the default character will be substituted if you draw
+ or measure text that contains characters which were not included in the font.
+ -->
+ <!-- <DefaultCharacter>*</DefaultCharacter> -->
+
+ <!--
+ CharacterRegions control what letters are available in the font. Every
+ character from Start to End will be built and made available for drawing. The
+ default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
+ character set. The characters are ordered according to the Unicode standard.
+ See the documentation for more information.
+ -->
+ <CharacterRegions>
+ <CharacterRegion>
+ <Start>&#32;</Start>
+ <End>&#126;</End>
+ </CharacterRegion>
+ </CharacterRegions>
+ </Asset>
+</XnaContent>
diff --git a/Content/branding/leafal/leafal-text-logo.png b/Content/branding/leafal/leafal-text-logo.png
new file mode 100644
index 0000000..3757373
--- /dev/null
+++ b/Content/branding/leafal/leafal-text-logo.png
Binary files differ
diff --git a/Content/branding/leafal/splash.mp3 b/Content/branding/leafal/splash.mp3
new file mode 100644
index 0000000..61149a4
--- /dev/null
+++ b/Content/branding/leafal/splash.mp3
Binary files differ
diff --git a/Content/branding/leafal/splash.mp3.sfk b/Content/branding/leafal/splash.mp3.sfk
new file mode 100644
index 0000000..cdde5aa
--- /dev/null
+++ b/Content/branding/leafal/splash.mp3.sfk
Binary files differ
diff --git a/Content/celesteia/title-rebrand-borders.png b/Content/celesteia/title-rebrand-borders.png
new file mode 100644
index 0000000..6698d44
--- /dev/null
+++ b/Content/celesteia/title-rebrand-borders.png
Binary files differ
diff --git a/Content/music/Landing Light.ogg b/Content/music/Landing Light.ogg
new file mode 100644
index 0000000..9020790
--- /dev/null
+++ b/Content/music/Landing Light.ogg
Binary files differ
diff --git a/Content/music/Stargaze Symphony.ogg b/Content/music/Stargaze Symphony.ogg
new file mode 100644
index 0000000..232848a
--- /dev/null
+++ b/Content/music/Stargaze Symphony.ogg
Binary files differ
diff --git a/Content/sprites/blockbreak.png b/Content/sprites/blockbreak.png
new file mode 100644
index 0000000..27e0e35
--- /dev/null
+++ b/Content/sprites/blockbreak.png
Binary files differ
diff --git a/Content/sprites/blocks.png b/Content/sprites/blocks.png
new file mode 100644
index 0000000..81c183a
--- /dev/null
+++ b/Content/sprites/blocks.png
Binary files differ
diff --git a/Content/sprites/blockselection.png b/Content/sprites/blockselection.png
new file mode 100644
index 0000000..81025a4
--- /dev/null
+++ b/Content/sprites/blockselection.png
Binary files differ
diff --git a/Content/sprites/crashed_capsule.png b/Content/sprites/crashed_capsule.png
new file mode 100644
index 0000000..334d877
--- /dev/null
+++ b/Content/sprites/crashed_capsule.png
Binary files differ
diff --git a/Content/sprites/entities/player/astronaut.png b/Content/sprites/entities/player/astronaut.png
new file mode 100644
index 0000000..6d9876f
--- /dev/null
+++ b/Content/sprites/entities/player/astronaut.png
Binary files differ
diff --git a/Content/sprites/items.png b/Content/sprites/items.png
new file mode 100644
index 0000000..27b9505
--- /dev/null
+++ b/Content/sprites/items.png
Binary files differ
diff --git a/Content/sprites/skybox/nebula.png b/Content/sprites/skybox/nebula.png
new file mode 100644
index 0000000..da2b259
--- /dev/null
+++ b/Content/sprites/skybox/nebula.png
Binary files differ
diff --git a/Content/sprites/skybox/shadow.png b/Content/sprites/skybox/shadow.png
new file mode 100644
index 0000000..ca44b51
--- /dev/null
+++ b/Content/sprites/skybox/shadow.png
Binary files differ
diff --git a/Content/sprites/skybox/stars.png b/Content/sprites/skybox/stars.png
new file mode 100644
index 0000000..22b5a67
--- /dev/null
+++ b/Content/sprites/skybox/stars.png
Binary files differ
diff --git a/Content/sprites/skybox/stars2.png b/Content/sprites/skybox/stars2.png
new file mode 100644
index 0000000..24e3809
--- /dev/null
+++ b/Content/sprites/skybox/stars2.png
Binary files differ
diff --git a/Content/sprites/ui.png b/Content/sprites/ui.png
new file mode 100644
index 0000000..5e25510
--- /dev/null
+++ b/Content/sprites/ui.png
Binary files differ
diff --git a/Content/sprites/ui/button.png b/Content/sprites/ui/button.png
new file mode 100644
index 0000000..b1a3671
--- /dev/null
+++ b/Content/sprites/ui/button.png
Binary files differ
diff --git a/Content/sprites/ui/window.png b/Content/sprites/ui/window.png
new file mode 100644
index 0000000..8f2bf06
--- /dev/null
+++ b/Content/sprites/ui/window.png
Binary files differ
diff --git a/Content/ttf/hobo.ttf b/Content/ttf/hobo.ttf
new file mode 100644
index 0000000..fd7a473
--- /dev/null
+++ b/Content/ttf/hobo.ttf
Binary files differ
diff --git a/GameInstance.cs b/GameInstance.cs
new file mode 100644
index 0000000..933bdda
--- /dev/null
+++ b/GameInstance.cs
@@ -0,0 +1,151 @@
+using System;
+using Celesteia.Screens;
+using Celesteia.Game.Input;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Celesteia.GUIs;
+using System.Collections.Generic;
+using Celesteia.Graphics;
+using MonoGame.Extended.Screens;
+using System.Linq;
+using Celesteia.Resources;
+using MonoGame.Extended.Screens.Transitions;
+using Celesteia.Resources.Collections;
+using Celesteia.Game.Music;
+using Celesteia.Game;
+
+namespace Celesteia
+{
+ public class GameInstance : Microsoft.Xna.Framework.Game
+ {
+ public static readonly string Version = "Alpha 1.3.1";
+ public static bool DebugMode { get; private set; }
+
+ private readonly List<string> cmdArgs;
+
+ private List<GUI> globalGUIs;
+
+ private readonly GraphicsManager _graphics;
+ private readonly ScreenManager _screenManager;
+ public readonly MusicManager Music;
+ public readonly WorldManager Worlds;
+ public readonly InputManager Input;
+
+ public SpriteBatch SpriteBatch;
+
+ public GameInstance()
+ {
+ // Add all components to the game instance.
+ Components.Add(_graphics = new GraphicsManager(this));
+ Components.Add(_screenManager = new ScreenManager());
+ Components.Add(Music = new MusicManager(this));
+ Components.Add(Worlds = new WorldManager(this));
+ Components.Add(Input = new InputManager(this));
+
+ // Load command line arguments into list.
+ cmdArgs = Environment.GetCommandLineArgs().ToList();
+
+ // Declare root of content management.
+ Content.RootDirectory = "Content";
+
+ // Make sure mouse is visible.
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ // Automatically enable debug mode when running a debug build.
+ #if DEBUG
+ DebugMode = true;
+ #endif
+
+ // Set up graphics and window (eventually from settings).
+ SetupGraphicsAndWindow();
+
+ // Run XNA native initialization logic.
+ base.Initialize();
+ }
+
+ private void SetupGraphicsAndWindow() {
+ _graphics.VSync = true;
+ _graphics.FullScreen = FullscreenMode.Windowed;
+ _graphics.Resolution = Window.ClientBounds;
+ _graphics.Apply();
+
+ // Disable slowdown on window focus loss.
+ InactiveSleepTime = new TimeSpan(0);
+
+ // Set maximum framerate to avoid resource soaking.
+ //IsFixedTimeStep = true;
+ //TargetElapsedTime = TimeSpan.FromSeconds(1 / 144.0);
+
+ // Allow game window to be resized, and set the title.
+ Window.AllowUserResizing = true;
+ Window.Title = $"Celesteia {Version}";
+
+ // Make sure the UI knows what game window to refer to for screen space calculations.
+ UIReferences.gameWindow = Window;
+ }
+
+ protected override void LoadContent()
+ {
+ SpriteBatch = new SpriteBatch(GraphicsDevice);
+
+ ResourceManager.AddCollection(new BaseCollection(Content));
+ ResourceManager.LoadContent(Content);
+
+ // Load global GUIs.
+ LoadGUI();
+
+ // Load the splash screen if it's a release build, load the game directly if it's a debug build.
+ if (cmdArgs.Contains("-gameplayDebug")) LoadScreen(new GameplayScreen(this, Worlds.LoadNewWorld((s) => Console.WriteLine(s)).GetAwaiter().GetResult()));
+ else if (cmdArgs.Contains("-textDebug")) LoadScreen(new TextTestScreen(this));
+ else LoadScreen(new SplashScreen(this));
+ }
+
+ private void LoadGUI() {
+ globalGUIs = new List<GUI>();
+
+ globalGUIs.Add(new DebugGUI(this));
+
+ // Load each global GUI.
+ globalGUIs.ForEach((gui) => { gui.LoadContent(Content); });
+ }
+
+ public void LoadScreen(GameScreen screen, Transition transition = null) {
+ // If a transition is present, load the screen using it.
+ if (transition != null) _screenManager.LoadScreen(screen, transition);
+
+ // If there was no transition specified, load the screen without it.
+ else _screenManager.LoadScreen(screen);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ // Update each global GUI.
+ globalGUIs.ForEach((gui) => { gui.Update(gameTime, out _); });
+
+ // If Scroll Lock is pressed, toggle GUIs.
+ if (KeyboardHelper.Pressed(Keys.Scroll)) UIReferences.GUIEnabled = !UIReferences.GUIEnabled;
+
+ // If F3 is pressed, toggle Debug Mode.
+ if (KeyboardHelper.Pressed(Keys.F3)) DebugMode = !DebugMode;
+
+ // If F11 is pressed, toggle Fullscreen.
+ if (KeyboardHelper.Pressed(Keys.F11)) {
+ _graphics.ToggleFullScreen();
+ _graphics.Apply();
+ }
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ base.Draw(gameTime);
+
+ globalGUIs.ForEach((gui) => { gui.Draw(gameTime); });
+ }
+ }
+}
diff --git a/Icon.bmp b/Icon.bmp
new file mode 100644
index 0000000..9dccb54
--- /dev/null
+++ b/Icon.bmp
Binary files differ
diff --git a/Icon.ico b/Icon.ico
new file mode 100644
index 0000000..b2ffd7e
--- /dev/null
+++ b/Icon.ico
Binary files differ
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e139b54
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Hazel Hofmann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..665254f
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Celesteia
+{
+ public static class Program
+ {
+ [STAThread]
+ static void Main()
+ {
+ using (var game = new GameInstance())
+ game.Run();
+ }
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b32b995
--- /dev/null
+++ b/README.md
@@ -0,0 +1,8 @@
+[![celesteia.com](./img/banner.jpg)](https://www.celesteia.com)
+
+# Celesteia
+This repository contains the source code for Celesteia.
+
+## Libraries Used
+- FastNoiseLite - https://github.com/Auburn/FastNoiseLite
+- MonoGame.Extended - https://github.com/craftworkgames/MonoGame.Extended
diff --git a/app.manifest b/app.manifest
new file mode 100644
index 0000000..f237739
--- /dev/null
+++ b/app.manifest
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.1" name="celesteia"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- A list of the Windows versions that this application has been tested on and is
+ is designed to work with. Uncomment the appropriate elements and Windows will
+ automatically selected the most compatible environment. -->
+
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+ </application>
+ </compatibility>
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+ </windowsSettings>
+ </application>
+
+</assembly>
diff --git a/img/background.jpg b/img/background.jpg
new file mode 100644
index 0000000..30cee80
--- /dev/null
+++ b/img/background.jpg
Binary files differ
diff --git a/img/banner.jpg b/img/banner.jpg
new file mode 100644
index 0000000..91920f9
--- /dev/null
+++ b/img/banner.jpg
Binary files differ
diff --git a/img/thumbnail.jpg b/img/thumbnail.jpg
new file mode 100644
index 0000000..c1285c8
--- /dev/null
+++ b/img/thumbnail.jpg
Binary files differ
diff --git a/source/game/WorldManager.cs b/source/game/WorldManager.cs
new file mode 100644
index 0000000..31040f8
--- /dev/null
+++ b/source/game/WorldManager.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Threading.Tasks;
+using Celesteia.Game.ECS;
+using Celesteia.Game.Planets;
+using Celesteia.Game.Planets.Generation;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game {
+ public class WorldManager : GameComponent
+ {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public WorldManager(GameInstance Game) : base(Game) {}
+
+ private GameWorld _loaded;
+
+ private GameWorld LoadWorld(GameWorld gameWorld) {
+ if (_loaded != null) _loaded.Dispose();
+
+ return _loaded = gameWorld;
+ }
+
+ public async Task<GameWorld> LoadNewWorld(Action<string> progressReport = null, int? seed = null) {
+ // Asynchronously generate the world.
+ GameWorld generatedWorld = await Task.Run<GameWorld>(() => {
+ GeneratedPlanet planet = new GeneratedPlanet(250, 75, seed);
+ planet.Generate(new TerranPlanetGenerator(planet), progressReport);
+ return new GameWorld(planet);
+ });
+
+ return LoadWorld(generatedWorld);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/components/CameraFollow.cs b/source/game/components/CameraFollow.cs
new file mode 100644
index 0000000..d6ae2f7
--- /dev/null
+++ b/source/game/components/CameraFollow.cs
@@ -0,0 +1,5 @@
+namespace Celesteia.Game.Components {
+ public class CameraFollow {
+ public float Weight = 1f;
+ }
+} \ No newline at end of file
diff --git a/source/game/components/EntityAttributes.cs b/source/game/components/EntityAttributes.cs
new file mode 100644
index 0000000..74a8e82
--- /dev/null
+++ b/source/game/components/EntityAttributes.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Linq;
+
+namespace Celesteia.Game.Components {
+ public class EntityAttributes {
+ public EntityAttributeMap Attributes;
+
+ public EntityAttributes(EntityAttributeMap attributes) {
+ Attributes = attributes;
+ }
+
+ public EntityAttributes() : this(new EntityAttributeMap()) {}
+
+ public partial class EntityAttributeMap {
+ private float[] attributes;
+
+ public EntityAttributeMap() {
+ attributes = new float[Enum.GetValues(typeof(EntityAttribute)).Cast<EntityAttribute>().Count()];
+ }
+
+ public EntityAttributeMap Set(EntityAttribute attribute, float value) {
+ attributes[(int) attribute] = value;
+ return this;
+ }
+
+ public float Get(EntityAttribute attribute) {
+ return attributes[(int) attribute];
+ }
+ }
+ }
+
+ public enum EntityAttribute {
+ MovementSpeed, JumpFuel, JumpForce, BlockRange
+ }
+} \ No newline at end of file
diff --git a/source/game/components/TargetPosition.cs b/source/game/components/TargetPosition.cs
new file mode 100644
index 0000000..f8b1a9c
--- /dev/null
+++ b/source/game/components/TargetPosition.cs
@@ -0,0 +1,7 @@
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Components {
+ public class TargetPosition {
+ public Vector2 Target;
+ }
+} \ No newline at end of file
diff --git a/source/game/components/entity/GameWorldEntity.cs b/source/game/components/entity/GameWorldEntity.cs
new file mode 100644
index 0000000..3087865
--- /dev/null
+++ b/source/game/components/entity/GameWorldEntity.cs
@@ -0,0 +1,14 @@
+using Celesteia.Game.ECS;
+
+namespace Celesteia.Game.Components.Entity {
+ public class GameWorldEntity {
+ private GameWorld _w;
+ private int _id;
+ public GameWorldEntity(GameWorld w, int id) {
+ _w = w;
+ _id = id;
+ }
+
+ public void Destroy() => _w.DestroyEntity(_id);
+ }
+} \ No newline at end of file
diff --git a/source/game/components/items/Inventory.cs b/source/game/components/items/Inventory.cs
new file mode 100644
index 0000000..4309f5d
--- /dev/null
+++ b/source/game/components/items/Inventory.cs
@@ -0,0 +1,96 @@
+using System;
+using Celesteia.Resources;
+
+namespace Celesteia.Game.Components.Items {
+ public class Inventory {
+ private ItemStack[] items;
+ public readonly int Capacity;
+
+ public Inventory(int slots = 27, params ItemStack[] startingItems) {
+ Capacity = slots;
+ items = new ItemStack[slots];
+
+ for (int i = 0; i < startingItems.Length; i++ ) items[i] = startingItems[i];
+ }
+
+ // Try adding an item to the inventory, return false if the inventory has no capacity for this action.
+ public bool AddItem(ItemStack stack) {
+ ItemStack existingStack = GetLastItemStack(stack.Key);
+
+ // If an item stack with this ID already exists, add the amount to that stack.
+ if (existingStack != null) {
+ existingStack.Amount += stack.Amount;
+
+ // If the max stack size was exceeded and a new stack has to be created, create it.
+ ItemStack newStack = existingStack.NewStack();
+ if (newStack != null) {
+ if (!HasCapacity()) return false;
+ existingStack.Amount = existingStack.Type.MaxStackSize;
+ AddItemStack(newStack);
+ }
+ } else AddItemStack(stack);
+
+ return true;
+ }
+
+ public ItemStack GetItemStackWithID(byte id) {
+ return Array.FindLast(items, x => x != null && x.ID == id && x.Amount < x.Type.MaxStackSize);
+ }
+
+ public ItemStack GetLastItemStack(NamespacedKey key) {
+ return Array.FindLast(items, x => x != null && x.Key.Equals(key) && x.Amount < x.Type.MaxStackSize);
+ }
+
+ public ItemStack GetSlot(int slot) {
+ if (slot < 0 || slot > Capacity - 1) throw new ArgumentException($"Slot {slot} falls outside of the inventory's capacity.");
+ return items[slot];
+ }
+
+ public void SetSlot(int slot, ItemStack stack) {
+ if (slot < 0 || slot > Capacity - 1) throw new ArgumentException($"Slot {slot} falls outside of the inventory's capacity.");
+ items[slot] = stack;
+ }
+
+ public bool ContainsAmount(NamespacedKey key, int amount) {
+ return GetAmount(key) >= amount;
+ }
+
+ public int GetAmount(NamespacedKey key) {
+ int amount = 0;
+
+ ItemStack[] stacksOfItem = Array.FindAll(items, x => x != null && x.Key.Equals(key));
+ foreach (ItemStack stackOfItem in stacksOfItem) amount += stackOfItem.Amount;
+
+ return amount;
+ }
+
+ public void RemoveAmount(NamespacedKey key, int amount) {
+ int amountToGo = amount;
+
+ ItemStack lastStack;
+ while (amountToGo > 0) {
+ lastStack = Array.FindLast(items, x => x != null && x.Key.Equals(key));
+ int toRemove = Math.Min(lastStack.Amount, amountToGo);
+
+ lastStack.Amount -= toRemove;
+ amountToGo -= toRemove;
+
+ AssertAmounts();
+ }
+ }
+
+ private void AddItemStack(ItemStack newStack) {
+ if (!HasCapacity()) return;
+ int i = Array.FindIndex(items, x => x == null);
+ items[i] = newStack;
+ }
+
+ private bool HasCapacity() {
+ return Array.Exists(items, x => x == null);
+ }
+
+ public void AssertAmounts() {
+ for (int i = 0; i < items.Length; i++) if (items[i] != null && items[i].Amount <= 0) items[i] = null;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/components/items/ItemStack.cs b/source/game/components/items/ItemStack.cs
new file mode 100644
index 0000000..360904b
--- /dev/null
+++ b/source/game/components/items/ItemStack.cs
@@ -0,0 +1,34 @@
+using Celesteia.Resources;
+using Celesteia.Resources.Types;
+
+namespace Celesteia.Game.Components.Items {
+ public class ItemStack {
+ public byte ID;
+ public NamespacedKey Key;
+ public int Amount;
+ public readonly ItemType Type;
+
+ public ItemStack(NamespacedKey key, int amount) {
+ Key = key;
+ Amount = amount;
+
+ Type = ResourceManager.Items.GetResource(key) as ItemType;
+ }
+
+ public ItemStack NewStack() {
+ ItemStack stack = null;
+ if (Amount > Type.MaxStackSize) stack = new ItemStack(Key, Amount - Type.MaxStackSize);
+
+ return stack;
+ }
+
+ public ItemStack Clone() {
+ return new ItemStack(Key, Amount);
+ }
+
+ public override string ToString()
+ {
+ return $"{Amount}x {Type.Name}";
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/components/physics/CollisionBox.cs b/source/game/components/physics/CollisionBox.cs
new file mode 100644
index 0000000..6e678ac
--- /dev/null
+++ b/source/game/components/physics/CollisionBox.cs
@@ -0,0 +1,36 @@
+using System;
+using MonoGame.Extended;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Components.Physics {
+ public class CollisionBox {
+ public RectangleF _bounds;
+ public RectangleF Bounds { get => _bounds; set => _bounds = value; }
+ private Rectangle _rounded;
+ public Rectangle Rounded { get => _rounded; }
+
+ public CollisionBox(float width, float height) {
+ _bounds = new RectangleF(-width / 2f, -height / 2f, width, height);
+ _rounded = Round(_bounds);
+ }
+
+ public void Update(Vector2 position) {
+ _bounds.X = position.X - (Bounds.Width / 2f);
+ _bounds.Y = position.Y - (Bounds.Height / 2f);
+ _rounded = Round(_bounds);
+ }
+
+ public Rectangle Round(RectangleF floatRect) {
+ return new Rectangle((int)MathF.Floor(floatRect.X), (int)MathF.Floor(floatRect.Y), (int)MathF.Ceiling(floatRect.Width), (int)MathF.Ceiling(floatRect.Height));
+ }
+
+ public RectangleF Intersection(CollisionBox other) {
+ return Intersection(other.Bounds);
+ }
+
+ public RectangleF Intersection(RectangleF other) {
+ RectangleF intersection = other.Intersection(Bounds);
+ return intersection;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/components/physics/PhysicsEntity.cs b/source/game/components/physics/PhysicsEntity.cs
new file mode 100644
index 0000000..7214dff
--- /dev/null
+++ b/source/game/components/physics/PhysicsEntity.cs
@@ -0,0 +1,38 @@
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Components.Physics {
+ public class PhysicsEntity {
+ public float Mass;
+
+ public bool Gravity;
+
+ public bool CollidingUp;
+ public bool CollidingLeft;
+ public bool CollidingRight;
+ public bool CollidingDown;
+
+ public PhysicsEntity(float mass, bool affectedByGravity) {
+ Mass = mass;
+ Gravity = affectedByGravity;
+ }
+
+ private Vector2 _velocity;
+ public Vector2 Velocity => _velocity;
+
+ public void SetVelocity(Vector2 vector) {
+ _velocity = vector;
+ }
+
+ public void SetVelocity(float x, float y) {
+ SetVelocity(new Vector2(x, y));
+ }
+
+ public void AddVelocity(Vector2 vector) {
+ _velocity += vector;
+ }
+
+ public void AddVelocity(float x, float y) {
+ AddVelocity(new Vector2(x, y));
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/components/player/LocalPlayer.cs b/source/game/components/player/LocalPlayer.cs
new file mode 100644
index 0000000..4780ec1
--- /dev/null
+++ b/source/game/components/player/LocalPlayer.cs
@@ -0,0 +1,5 @@
+namespace Celesteia.Game.Components.Player {
+ public class LocalPlayer {
+ public float JumpRemaining = .5f;
+ }
+} \ No newline at end of file
diff --git a/source/game/components/player/PlayerInput.cs b/source/game/components/player/PlayerInput.cs
new file mode 100644
index 0000000..96f3a57
--- /dev/null
+++ b/source/game/components/player/PlayerInput.cs
@@ -0,0 +1,14 @@
+using Celesteia.Game.Input.Conditions;
+
+namespace Celesteia.Game.Components.Player {
+ public class PlayerInput {
+ public ICondition<float> Horizontal;
+ public ICondition<bool> Run;
+ public ICondition<bool> Jump;
+ public ICondition<bool> Inventory;
+ public ICondition<bool> Crafting;
+ public ICondition<bool> Pause;
+ public ICondition<bool> PrimaryUse;
+ public ICondition<bool> SecondaryUse;
+ }
+} \ No newline at end of file
diff --git a/source/game/components/skybox/SkyboxRotateZ.cs b/source/game/components/skybox/SkyboxRotateZ.cs
new file mode 100644
index 0000000..151aec9
--- /dev/null
+++ b/source/game/components/skybox/SkyboxRotateZ.cs
@@ -0,0 +1,17 @@
+namespace Celesteia.Game.Components.Skybox {
+ public class SkyboxRotateZ {
+ public float _magnitude = 1f;
+ // Amount to rotate by.
+ public float Magnitude { get { return _magnitude; } }
+
+ // Current rotation.
+ public float Current = 0f;
+
+ public SkyboxRotateZ(float magnitude) {
+ _magnitude = magnitude;
+
+ // Offset the starting rotation.
+ Current = _magnitude * 256f;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/ecs/EntityFactory.cs b/source/game/ecs/EntityFactory.cs
new file mode 100644
index 0000000..482b137
--- /dev/null
+++ b/source/game/ecs/EntityFactory.cs
@@ -0,0 +1,111 @@
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using Celesteia.Resources.Sprites;
+using Celesteia.Game.Components.Player;
+using MonoGame.Extended.TextureAtlases;
+using Microsoft.Xna.Framework.Graphics;
+using Celesteia.Game.Input;
+using Celesteia.Game.Components;
+using Microsoft.Xna.Framework.Input;
+using Celesteia.Game.Components.Physics;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Components.Skybox;
+using Celesteia.Resources.Types;
+using Celesteia.Game.Input.Definitions.Keyboard;
+using Celesteia.Game.Input.Definitions.Gamepad;
+using Celesteia.Game.Input.Definitions.Mouse;
+using MonoGame.Extended.Input;
+using Celesteia.Game.Input.Definitions;
+using Celesteia.Game.Input.Conditions;
+
+namespace Celesteia.Game.ECS {
+ /*
+ Contains various commonly used prefabrications for entities.
+ Many of the functions were moved to EntityTypes.
+ */
+
+ public class EntityFactory {
+ private readonly GameWorld _gameWorld;
+
+ public EntityFactory(GameWorld gameWorld) => _gameWorld = gameWorld;
+
+ public Entity CreateEntity(NamespacedKey key) => CreateEntity(ResourceManager.Entities.GetResource(key) as EntityType);
+
+ public Entity CreateEntity(EntityType type)
+ {
+ Entity entity = _gameWorld.CreateEntity();
+ type.Instantiate(entity);
+
+ return entity;
+ }
+
+ public static void BuildPlayer(Entity entity, Texture2D sprites) {
+ entity.Attach(new Transform2());
+
+ entity.Attach(new TargetPosition());
+
+ entity.Attach(new EntityFrames(
+ TextureAtlas.Create("player", sprites, 24, 24),
+ 0, 2,
+ ResourceManager.SPRITE_SCALING
+ ));
+
+ entity.Attach(new Inventory(36,
+ new ItemStack(NamespacedKey.Base("iron_pickaxe"), 1),
+ new ItemStack(NamespacedKey.Base("wooden_torch"), 10)
+ ));
+
+ entity.Attach(new PhysicsEntity(1.6f, true));
+
+ entity.Attach(new CollisionBox(1.5f, 3f));
+
+ entity.Attach(new PlayerInput() {
+ Horizontal = new AverageCondition(
+ new TrinaryKeyboardDefinition() { Negative = Keys.A, Positive = Keys.D, PollType = InputPollType.Held },
+ new SensorGamepadDefinition() { Sensor = GamePadSensor.LeftThumbStickX }
+ ),
+ Run = new AnyCondition(
+ new BinaryKeyboardDefinition() { Keys = Keys.LeftShift, PollType = InputPollType.Held },
+ new BinaryGamepadDefinition() { Buttons = Buttons.LeftShoulder, PollType = InputPollType.Held }
+ ),
+ Jump = new AnyCondition(
+ new BinaryKeyboardDefinition() { Keys = Keys.Space, PollType = InputPollType.Held },
+ new BinaryGamepadDefinition() { Buttons = Buttons.A, PollType = InputPollType.Held }
+ ),
+ Inventory = new AnyCondition(
+ new BinaryKeyboardDefinition() { Keys = Keys.B, PollType = InputPollType.Pressed },
+ new BinaryGamepadDefinition() { Buttons = Buttons.Y, PollType = InputPollType.Pressed }
+ ),
+ Crafting = new AnyCondition(
+ new BinaryKeyboardDefinition() { Keys = Keys.C, PollType = InputPollType.Pressed },
+ new BinaryGamepadDefinition() { Buttons = Buttons.X, PollType = InputPollType.Pressed }
+ ),
+ Pause = new AnyCondition(
+ new BinaryKeyboardDefinition() { Keys = Keys.Escape, PollType = InputPollType.Pressed },
+ new BinaryGamepadDefinition() { Buttons = Buttons.Start, PollType = InputPollType.Pressed }
+ ),
+ PrimaryUse = new AnyCondition(
+ new BinaryMouseDefinition() { Button = MouseButton.Left, PollType = InputPollType.Held },
+ new BinaryGamepadDefinition() { Buttons = Buttons.RightTrigger, PollType = InputPollType.Held }
+ ),
+ SecondaryUse = new AnyCondition(
+ new BinaryMouseDefinition() { Button = MouseButton.Right, PollType = InputPollType.Held },
+ new BinaryGamepadDefinition() { Buttons = Buttons.LeftTrigger, PollType = InputPollType.Held }
+ )
+ });
+
+ entity.Attach(new LocalPlayer());
+
+ entity.Attach(new CameraFollow());
+
+ entity.Attach(new EntityAttributes(new EntityAttributes.EntityAttributeMap()
+ .Set(EntityAttribute.MovementSpeed, 12.5f)
+ .Set(EntityAttribute.JumpFuel, .5f)
+ .Set(EntityAttribute.JumpForce, 10f)
+ .Set(EntityAttribute.BlockRange, 7f)
+ ));
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/ecs/GameWorld.cs b/source/game/ecs/GameWorld.cs
new file mode 100644
index 0000000..9ee516e
--- /dev/null
+++ b/source/game/ecs/GameWorld.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using Celesteia.Game.Components.Entity;
+using Celesteia.Game.Planets;
+using Celesteia.Game.Systems;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.ECS {
+ public class GameWorld : IDisposable {
+ public ChunkMap ChunkMap { get; private set; }
+ public GameWorld(ChunkMap map) => ChunkMap = map;
+
+ public void Dispose() {
+ _w.Dispose();
+ ChunkMap = null;
+ }
+
+ private World _w;
+ private WorldBuilder _builder;
+
+ public WorldBuilder BeginBuilder() => _builder = new WorldBuilder();
+
+ public WorldBuilder AddSystem(ISystem system) => _builder.AddSystem(system);
+
+ public void EndBuilder() => _w = _builder.Build();
+
+ public Entity CreateEntity() {
+ Entity e = _w.CreateEntity();
+ e.Attach(new GameWorldEntity(this, e.Id));
+
+ return e;
+ }
+
+ public void DestroyEntity(int id) => _w.DestroyEntity(id);
+
+ public void Update(GameTime gt) => _w.Update(gt);
+ public void Draw(GameTime gt) => _w.Draw(gt);
+ }
+} \ No newline at end of file
diff --git a/source/game/input/GamepadHelper.cs b/source/game/input/GamepadHelper.cs
new file mode 100644
index 0000000..dfc3175
--- /dev/null
+++ b/source/game/input/GamepadHelper.cs
@@ -0,0 +1,59 @@
+using System;
+using Celesteia.Game.Input.Definitions;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+
+namespace Celesteia.Game.Input {
+ public static class GamepadHelper {
+ private static GamePadState _prev;
+ private static GamePadState _curr;
+
+ public static void Update() {
+ _prev = _curr;
+ _curr = GamePad.GetState(0);
+ }
+
+ public static bool AnyButton() {
+ foreach (Buttons b in Enum.GetValues<Buttons>())
+ if (IsDown(b)) return true;
+
+ return false;
+ }
+
+ // Was true, now true.
+ public static bool Held(Buttons buttons) => _prev.IsButtonDown(buttons) && _curr.IsButtonDown(buttons);
+ // Is true.
+ public static bool IsDown(Buttons buttons) => _curr.IsButtonDown(buttons);
+ // Was false, now true.
+ public static bool Pressed(Buttons buttons) => !_prev.IsButtonDown(buttons) && _curr.IsButtonDown(buttons);
+ // Was true, now false.
+ public static bool Released(Buttons buttons) => _prev.IsButtonDown(buttons) && !_curr.IsButtonDown(buttons);
+
+ public static bool PollButtons(Buttons buttons, InputPollType type) => type switch {
+ InputPollType.Held => Held(buttons),
+ InputPollType.IsDown => IsDown(buttons),
+ InputPollType.Pressed => Pressed(buttons),
+ InputPollType.Released => Released(buttons),
+ _ => false
+ };
+
+ public static float PollSensor(GamePadSensor sensor) => sensor switch {
+ GamePadSensor.LeftThumbStickX => _curr.ThumbSticks.Left.X,
+ GamePadSensor.LeftThumbStickY => _curr.ThumbSticks.Left.Y,
+ GamePadSensor.LeftTrigger => _curr.Triggers.Left,
+ GamePadSensor.RightThumbStickX => _curr.ThumbSticks.Right.X,
+ GamePadSensor.RightThumbStickY => _curr.ThumbSticks.Right.Y,
+ GamePadSensor.RightTrigger => _curr.Triggers.Right,
+ _ => 0f
+ };
+ }
+
+ public enum GamePadSensor {
+ LeftThumbStickX,
+ LeftThumbStickY,
+ LeftTrigger,
+ RightThumbStickX,
+ RightThumbStickY,
+ RightTrigger
+ }
+} \ No newline at end of file
diff --git a/source/game/input/InputManager.cs b/source/game/input/InputManager.cs
new file mode 100644
index 0000000..1bd3e38
--- /dev/null
+++ b/source/game/input/InputManager.cs
@@ -0,0 +1,33 @@
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Input {
+ public class InputManager : GameComponent {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public InputManager(GameInstance Game) : base(Game) {}
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ if (!Enabled) return;
+
+ KeyboardHelper.Update();
+ GamepadHelper.Update();
+ MouseHelper.Update();
+
+ base.Update(gameTime);
+ }
+
+ public bool GetAny() {
+ return KeyboardHelper.AnyKey() || GamepadHelper.AnyButton();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/input/KeyboardHelper.cs b/source/game/input/KeyboardHelper.cs
new file mode 100644
index 0000000..a8fd4f0
--- /dev/null
+++ b/source/game/input/KeyboardHelper.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using System.Linq;
+using Celesteia.Game.Input.Definitions;
+using Microsoft.Xna.Framework.Input;
+using MonoGame.Extended.Input;
+
+namespace Celesteia.Game.Input {
+ public static class KeyboardHelper {
+ private static KeyboardStateExtended _prev;
+ private static KeyboardStateExtended _curr;
+
+ public static void Update() {
+ _prev = _curr;
+ _curr = KeyboardExtended.GetState();
+ }
+
+ // Is any key (excluding the exemptions) down in the current state?
+ public static List<Keys> exemptedFromAny = new List<Keys> {
+ { Keys.F3 }, { Keys.F11 }
+ };
+
+ private static Keys[] pressed;
+ public static bool AnyKey() {
+ pressed = _curr.GetPressedKeys();
+ bool anyKey = pressed.Length > 0;
+
+ if (anyKey)
+ for (int i = 0; i < exemptedFromAny.Count; i++)
+ anyKey = !pressed.Contains(exemptedFromAny[i]);
+
+ return anyKey;
+ }
+
+ // Was true, now true.
+ public static bool Held(Keys keys) => _prev.IsKeyDown(keys) && _curr.IsKeyDown(keys);
+ // Is true.
+ public static bool IsDown(Keys keys) => _curr.IsKeyDown(keys);
+ // Was false, now true.
+ public static bool Pressed(Keys keys) => !_prev.IsKeyDown(keys) && _curr.IsKeyDown(keys);
+ // Was true, now false.
+ public static bool Released(Keys keys) => _prev.IsKeyDown(keys) && !_curr.IsKeyDown(keys);
+
+ public static bool Poll(Keys keys, InputPollType type) => type switch {
+ InputPollType.Held => Held(keys),
+ InputPollType.IsDown => IsDown(keys),
+ InputPollType.Pressed => Pressed(keys),
+ InputPollType.Released => Released(keys),
+ _ => false
+ };
+ }
+
+
+} \ No newline at end of file
diff --git a/source/game/input/MouseHelper.cs b/source/game/input/MouseHelper.cs
new file mode 100644
index 0000000..3000c6e
--- /dev/null
+++ b/source/game/input/MouseHelper.cs
@@ -0,0 +1,37 @@
+using Celesteia.Game.Input.Definitions;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Input;
+
+namespace Celesteia.Game.Input {
+ public static class MouseHelper {
+ private static MouseStateExtended _prev;
+ private static MouseStateExtended _curr;
+
+ public static int ScrollDelta => _curr.ScrollWheelValue - _prev.ScrollWheelValue;
+
+ // Get the position of the mouse pointer on the SCREEN, not the VIEWPORT.
+ public static Point Position => _curr.Position;
+
+ public static void Update() {
+ _prev = _curr;
+ _curr = MouseExtended.GetState();
+ }
+
+ // Was true, now true.
+ public static bool Held(MouseButton button) => _prev.IsButtonDown(button) && _curr.IsButtonDown(button);
+ // Is true.
+ public static bool IsDown(MouseButton button) => _curr.IsButtonDown(button);
+ // Was false, now true.
+ public static bool Pressed(MouseButton button) => !_prev.IsButtonDown(button) && _curr.IsButtonDown(button);
+ // Was true, now false.
+ public static bool Released(MouseButton button) => _prev.IsButtonDown(button) && !_curr.IsButtonDown(button);
+
+ public static bool Poll(MouseButton button, InputPollType type) => type switch {
+ InputPollType.Held => Held(button),
+ InputPollType.IsDown => IsDown(button),
+ InputPollType.Pressed => Pressed(button),
+ InputPollType.Released => Released(button),
+ _ => false
+ };
+ }
+} \ No newline at end of file
diff --git a/source/game/input/conditions/AllCondition.cs b/source/game/input/conditions/AllCondition.cs
new file mode 100644
index 0000000..183acfb
--- /dev/null
+++ b/source/game/input/conditions/AllCondition.cs
@@ -0,0 +1,15 @@
+using Celesteia.Game.Input.Definitions;
+
+namespace Celesteia.Game.Input.Conditions {
+ public class AllCondition : ICondition<bool> {
+ private IBinaryInputDefinition[] _definitions;
+
+ public AllCondition(params IBinaryInputDefinition[] definitions)
+ => _definitions = definitions;
+
+ public bool Poll() {
+ for (int i = 0; i < _definitions.Length; i++) if (!_definitions[i].Test()) return false;
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/input/conditions/AnyCondition.cs b/source/game/input/conditions/AnyCondition.cs
new file mode 100644
index 0000000..a870539
--- /dev/null
+++ b/source/game/input/conditions/AnyCondition.cs
@@ -0,0 +1,15 @@
+using Celesteia.Game.Input.Definitions;
+
+namespace Celesteia.Game.Input.Conditions {
+ public class AnyCondition : ICondition<bool> {
+ private IBinaryInputDefinition[] _definitions;
+
+ public AnyCondition(params IBinaryInputDefinition[] definitions)
+ => _definitions = definitions;
+
+ public bool Poll() {
+ for (int i = 0; i < _definitions.Length; i++) if (_definitions[i].Test()) return true;
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/input/conditions/AverageCondition.cs b/source/game/input/conditions/AverageCondition.cs
new file mode 100644
index 0000000..08abdb7
--- /dev/null
+++ b/source/game/input/conditions/AverageCondition.cs
@@ -0,0 +1,16 @@
+using Celesteia.Game.Input.Definitions;
+
+namespace Celesteia.Game.Input.Conditions {
+ public class AverageCondition : ICondition<float> {
+ private IFloatInputDefinition[] _definitions;
+
+ public AverageCondition(params IFloatInputDefinition[] definitions)
+ => _definitions = definitions;
+
+ public float Poll() {
+ float total = 0f;
+ for (int i = 0; i < _definitions.Length; i++) total += _definitions[i].Test();
+ return total / _definitions.Length;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/input/conditions/ICondition.cs b/source/game/input/conditions/ICondition.cs
new file mode 100644
index 0000000..32bbc74
--- /dev/null
+++ b/source/game/input/conditions/ICondition.cs
@@ -0,0 +1,5 @@
+namespace Celesteia.Game.Input.Conditions {
+ public interface ICondition<T> {
+ T Poll();
+ }
+} \ No newline at end of file
diff --git a/source/game/input/definitions/InputDefinition.cs b/source/game/input/definitions/InputDefinition.cs
new file mode 100644
index 0000000..7d56f99
--- /dev/null
+++ b/source/game/input/definitions/InputDefinition.cs
@@ -0,0 +1,16 @@
+namespace Celesteia.Game.Input.Definitions {
+ public interface IInputDefinition<T> {
+ T Test();
+ }
+
+ public interface IBinaryInputDefinition : IInputDefinition<bool> {}
+ public interface IFloatInputDefinition : IInputDefinition<float> {}
+
+ public enum InputType {
+ Keyboard, Gamepad
+ }
+
+ public enum InputPollType {
+ IsDown, Held, Pressed, Released
+ }
+} \ No newline at end of file
diff --git a/source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs b/source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs
new file mode 100644
index 0000000..5aec7a8
--- /dev/null
+++ b/source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs
@@ -0,0 +1,10 @@
+using Microsoft.Xna.Framework.Input;
+
+namespace Celesteia.Game.Input.Definitions.Gamepad {
+ public class BinaryGamepadDefinition : IBinaryInputDefinition {
+ public Buttons Buttons;
+ public InputPollType PollType;
+
+ public bool Test() => GamepadHelper.PollButtons(Buttons, PollType);
+ }
+} \ No newline at end of file
diff --git a/source/game/input/definitions/gamepad/SensorGamepadDefinition.cs b/source/game/input/definitions/gamepad/SensorGamepadDefinition.cs
new file mode 100644
index 0000000..ebbf2be
--- /dev/null
+++ b/source/game/input/definitions/gamepad/SensorGamepadDefinition.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Celesteia.Game.Input.Definitions.Gamepad {
+ public class SensorGamepadDefinition : IFloatInputDefinition {
+ public GamePadSensor Sensor;
+ private float _current = 0;
+
+ public float Test() {
+ _current = GamepadHelper.PollSensor(Sensor);
+ return _current;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs b/source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs
new file mode 100644
index 0000000..87ebb77
--- /dev/null
+++ b/source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs
@@ -0,0 +1,11 @@
+using System;
+using Microsoft.Xna.Framework.Input;
+
+namespace Celesteia.Game.Input.Definitions.Keyboard {
+ public class BinaryKeyboardDefinition : IBinaryInputDefinition {
+ public Keys Keys;
+ public InputPollType PollType;
+
+ public bool Test() => KeyboardHelper.Poll(Keys, PollType);
+ }
+} \ No newline at end of file
diff --git a/source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs b/source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs
new file mode 100644
index 0000000..7439859
--- /dev/null
+++ b/source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs
@@ -0,0 +1,18 @@
+using Microsoft.Xna.Framework.Input;
+
+namespace Celesteia.Game.Input.Definitions.Keyboard {
+ public class TrinaryKeyboardDefinition : IFloatInputDefinition {
+ public Keys Negative;
+ public Keys Positive;
+ public InputPollType PollType;
+
+ private float _current = 0;
+
+ public float Test() {
+ _current =
+ (KeyboardHelper.Poll(Negative, PollType) ? -1 : 0) +
+ (KeyboardHelper.Poll(Positive, PollType) ? 1 : 0);
+ return _current;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/input/definitions/mouse/BinaryMouseDefinition.cs b/source/game/input/definitions/mouse/BinaryMouseDefinition.cs
new file mode 100644
index 0000000..0008ded
--- /dev/null
+++ b/source/game/input/definitions/mouse/BinaryMouseDefinition.cs
@@ -0,0 +1,10 @@
+using MonoGame.Extended.Input;
+
+namespace Celesteia.Game.Input.Definitions.Mouse {
+ public class BinaryMouseDefinition : IBinaryInputDefinition {
+ public MouseButton Button;
+ public InputPollType PollType;
+
+ public bool Test() => MouseHelper.Poll(Button, PollType);
+ }
+} \ No newline at end of file
diff --git a/source/game/items/BlockItemActions.cs b/source/game/items/BlockItemActions.cs
new file mode 100644
index 0000000..94b6c67
--- /dev/null
+++ b/source/game/items/BlockItemActions.cs
@@ -0,0 +1,73 @@
+using Celesteia.Game.Components;
+using Celesteia.Game.Components.Physics;
+using Celesteia.Game.Planets;
+using Celesteia.Resources;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public class BlockItemActions : CooldownItemActions {
+ protected BlockType type;
+ protected byte id = 0;
+
+ public BlockItemActions(NamespacedKey blockKey) {
+ UseTime = 0;
+ type = ResourceManager.Blocks.GetResource(blockKey) as BlockType;
+ id = type.GetID();
+ }
+
+ public override bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user)
+ => Assert(gameTime, chunkMap, cursor, user, false) && Place(chunkMap, cursor, false);
+
+ public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user)
+ => Assert(gameTime, chunkMap, cursor, user, true) && Place(chunkMap, cursor, true);
+
+ public virtual bool Assert(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user, bool forWall) {
+ if (!base.Assert(gameTime)) return false;
+ if (id == 0) return false;
+
+ UpdateLastUse(gameTime);
+
+ if (!user.Has<Transform2>() || !user.Has<EntityAttributes>()) return false;
+
+ Transform2 entityTransform = user.Get<Transform2>();
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().Attributes;
+
+ if (Vector2.Distance(entityTransform.Position, cursor.ToVector2()) > attributes.Get(EntityAttribute.BlockRange)) return false;
+
+ if (!forWall && user.Has<CollisionBox>()) {
+ Rectangle box = user.Get<CollisionBox>().Rounded;
+ RectangleF? rect = chunkMap.TestBoundingBox(cursor.X, cursor.Y, type.BoundingBox);
+ if (rect.HasValue) {
+ bool intersect = rect.Intersects(new RectangleF(box.X, box.Y, box.Width, box.Height));
+ if (intersect) return false;
+ }
+ }
+
+ if (!(forWall ?
+ chunkMap.GetBackground(cursor.X, cursor.Y) :
+ chunkMap.GetForeground(cursor.X, cursor.Y))
+ .Empty) return false;
+
+ // Require adjacent blocks or a filled tile on the other layer.
+ return (
+ chunkMap.GetAny(cursor.X - 1, cursor.Y) ||
+ chunkMap.GetAny(cursor.X + 1, cursor.Y) ||
+ chunkMap.GetAny(cursor.X, cursor.Y - 1) ||
+ chunkMap.GetAny(cursor.X, cursor.Y + 1)
+ ) || (forWall ?
+ chunkMap.GetForeground(cursor.X, cursor.Y) :
+ chunkMap.GetBackground(cursor.X, cursor.Y))
+ .Empty;
+ }
+
+ public bool Place(ChunkMap chunkMap, Point cursor, bool wall) {
+ if (wall) chunkMap.SetBackgroundID(cursor, id);
+ else chunkMap.SetForegroundID(cursor, id);
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/items/CooldownItemActions.cs b/source/game/items/CooldownItemActions.cs
new file mode 100644
index 0000000..647661e
--- /dev/null
+++ b/source/game/items/CooldownItemActions.cs
@@ -0,0 +1,17 @@
+using Celesteia.Game.Planets;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public class CooldownItemActions : IItemActions {
+ public double UseTime = 1.0;
+ public double LastUse = 0.0;
+ public void UpdateLastUse(GameTime gameTime) => LastUse = gameTime.TotalGameTime.TotalSeconds;
+ public bool CheckUseTime(GameTime gameTime) => gameTime.TotalGameTime.TotalSeconds - LastUse >= UseTime;
+
+ public virtual bool Assert(GameTime gameTime) => CheckUseTime(gameTime);
+
+ public virtual bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false;
+ public virtual bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false;
+ }
+} \ No newline at end of file
diff --git a/source/game/items/FoliageItemActions.cs b/source/game/items/FoliageItemActions.cs
new file mode 100644
index 0000000..54bde6d
--- /dev/null
+++ b/source/game/items/FoliageItemActions.cs
@@ -0,0 +1,18 @@
+using Celesteia.Game.Planets;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public class FoliageItemActions : BlockItemActions {
+ byte grown_soil;
+ public FoliageItemActions(NamespacedKey blockKey) : base(blockKey) {
+ grown_soil = ResourceManager.Blocks.GetResource(NamespacedKey.Base("grown_soil")).GetID();
+ }
+
+ public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false;
+
+ public override bool Assert(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user, bool forWall)
+ => chunkMap.GetForeground(cursor.X, cursor.Y + 1).BlockID == grown_soil && base.Assert(gameTime, chunkMap, cursor, user, false);
+ }
+} \ No newline at end of file
diff --git a/source/game/items/IItemActions.cs b/source/game/items/IItemActions.cs
new file mode 100644
index 0000000..5a95f06
--- /dev/null
+++ b/source/game/items/IItemActions.cs
@@ -0,0 +1,11 @@
+using Celesteia.Game.Planets;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public interface IItemActions {
+
+ public bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user);
+ public bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user);
+ }
+} \ No newline at end of file
diff --git a/source/game/items/PickaxeItemActions.cs b/source/game/items/PickaxeItemActions.cs
new file mode 100644
index 0000000..56e2714
--- /dev/null
+++ b/source/game/items/PickaxeItemActions.cs
@@ -0,0 +1,63 @@
+using Celesteia.Game.Components;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Planets;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public class PickaxeItemActions : CooldownItemActions {
+ private int _power;
+
+ public PickaxeItemActions(int power) {
+ UseTime = 0.2;
+ _power = power;
+ }
+
+ public override bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user)
+ => Assert(gameTime, chunkMap, cursor, user, false) && Break(chunkMap, cursor, user, false);
+
+ public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user)
+ => Assert(gameTime, chunkMap, cursor, user, true) && Break(chunkMap, cursor, user, true);
+
+ // Check if the conditions to use this item's action are met.
+ public bool Assert(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user, bool forWall) {
+ if (!base.Assert(gameTime)) return false;
+
+ // If the user has no transform or attributes, the rest of the function will not work, so check if they're there first.
+ if (!user.Has<Transform2>() || !user.Has<EntityAttributes>()) return false;
+
+ Transform2 entityTransform = user.Get<Transform2>();
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().Attributes;
+
+ // Check if the targeted location is within the entity's block manipulation range.
+ if (Vector2.Distance(entityTransform.Position, cursor.ToVector2()) > attributes.Get(EntityAttribute.BlockRange)) return false;
+
+ BlockState state = (forWall ? chunkMap.GetBackground(cursor) : chunkMap.GetForeground(cursor));
+
+ // If there is no tile in the given location, the action will not continue.
+ if (state.Empty) return false;
+
+ // If the block is unbreakable, don't break it. Duh.
+ if (state.Type.Strength < 0) return false;
+
+ UpdateLastUse(gameTime);
+
+ return true;
+ }
+
+
+ public bool Break(ChunkMap chunkMap, Point cursor, Entity user, bool wall) {
+ // Add breaking progress accordingly.
+ ItemStack drops = null;
+ if (wall) chunkMap.AddBackgroundBreakProgress(cursor, _power, out drops);
+ else chunkMap.AddForegroundBreakProgress(cursor, _power, out drops);
+
+ if (drops != null && user.Has<Inventory>())
+ user.Get<Inventory>().AddItem(drops);
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/items/TorchItemActions.cs b/source/game/items/TorchItemActions.cs
new file mode 100644
index 0000000..12eb8ed
--- /dev/null
+++ b/source/game/items/TorchItemActions.cs
@@ -0,0 +1,15 @@
+using Celesteia.Game.Planets;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public class TorchItemActions : BlockItemActions {
+ public TorchItemActions(NamespacedKey blockKey) : base(blockKey) {}
+
+ public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false;
+
+ public override bool Assert(GameTime g, ChunkMap cm, Point c, Entity u, bool wa = false)
+ => !cm.GetBackground(c).Empty && base.Assert(g, cm, c, u, false);
+ }
+} \ No newline at end of file
diff --git a/source/game/items/UpgradeItemActions.cs b/source/game/items/UpgradeItemActions.cs
new file mode 100644
index 0000000..18d922d
--- /dev/null
+++ b/source/game/items/UpgradeItemActions.cs
@@ -0,0 +1,48 @@
+using System;
+using Celesteia.Game.Components;
+using Celesteia.Game.Planets;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Game.Items {
+ public class UpgradeItemActions : CooldownItemActions {
+ private float _increase;
+ private EntityAttribute _attribute;
+ private float _max;
+
+ public UpgradeItemActions(EntityAttribute attribute, float increase, float max) {
+ UseTime = 0.2;
+ _attribute = attribute;
+ _increase = increase;
+ _max = max;
+ }
+
+ public override bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => Assert(gameTime, user) && Use(user);
+
+ // Check if the conditions to use this item's action are met.
+ public bool Assert(GameTime gameTime, Entity user) {
+ if (!base.Assert(gameTime)) return false;
+
+ // If the user has no attributes, the rest of the function will not work, so check if they're there first.
+ if (!user.Has<EntityAttributes>()) return false;
+
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().Attributes;
+
+ // If the attribute is maxed out, don't upgrade.
+ if (attributes.Get(_attribute) >= _max) return false;
+
+ UpdateLastUse(gameTime);
+
+ return true;
+ }
+
+
+ public bool Use(Entity user) {
+ // Use the upgrade.
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().Attributes;
+ attributes.Set(_attribute, Math.Clamp(attributes.Get(_attribute) + _increase, 0f, _max));
+
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/music/MusicManager.cs b/source/game/music/MusicManager.cs
new file mode 100644
index 0000000..8d9f9f5
--- /dev/null
+++ b/source/game/music/MusicManager.cs
@@ -0,0 +1,54 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Media;
+
+namespace Celesteia.Game.Music {
+ public class MusicManager : GameComponent {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public MusicManager(GameInstance Game) : base(Game) {}
+
+ private float SetVolume = 0.1f;
+ private float _volume;
+ private Song _nextUp;
+ private float _elapsedTransitionTime;
+ private float _transitionDuration = 2f;
+ private bool _transitionComplete = false;
+
+ public override void Update(GameTime gameTime)
+ {
+ if (_elapsedTransitionTime >= _transitionDuration) _volume = 1f;
+ else {
+ _elapsedTransitionTime += ((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f);
+
+ if (_elapsedTransitionTime >= _transitionDuration / 2f && !_transitionComplete) {
+ if (_nextUp != null) {
+ MediaPlayer.Play(_nextUp);
+ _nextUp = null;
+ } else Stop();
+
+ _transitionComplete = true;
+ }
+
+ _volume = Volume(_elapsedTransitionTime);
+ }
+
+ MediaPlayer.Volume = _volume * SetVolume;
+ }
+
+ public void PlayNow(Song song, bool repeat = true) {
+ MediaPlayer.IsRepeating = repeat;
+
+ _elapsedTransitionTime = MediaPlayer.State == MediaState.Playing ? 0f : 1f;
+ _transitionComplete = false;
+ _nextUp = song;
+ }
+
+ public void Stop() {
+ MediaPlayer.Stop();
+ }
+
+ private float Volume(float x) {
+ return Math.Abs(x - (_transitionDuration / 2f));
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/BlockState.cs b/source/game/planets/BlockState.cs
new file mode 100644
index 0000000..e7d4be0
--- /dev/null
+++ b/source/game/planets/BlockState.cs
@@ -0,0 +1,43 @@
+using Celesteia.Game.Components.Items;
+using Celesteia.Resources;
+using Celesteia.Resources.Sprites;
+using Celesteia.Resources.Types;
+
+namespace Celesteia.Game.Planets {
+ public struct BlockState {
+ public static BlockState None = new BlockState() { Empty = true };
+
+ public bool Empty;
+
+ private byte _id;
+ public byte BlockID {
+ get => _id;
+ set {
+ _id = value;
+ Type = ResourceManager.Blocks.GetBlock(BlockID) as BlockType;
+
+ Empty = _id == 0;
+ Translucent = Type.Translucent;
+ Frames = Type.Frames;
+ }
+ }
+
+ public BlockType Type { get; private set; }
+ public bool Translucent { get; private set; }
+ public BlockFrames Frames { get; private set; }
+
+ public int BreakProgress;
+
+ public bool Draw;
+ public bool HasFrames() => Frames != null;
+
+ public BlockData Data;
+ public bool HasData() => Data != null;
+ }
+
+ public class BlockData {}
+ public class TileEntity : BlockData {}
+ public class Container : TileEntity {
+ public Inventory inventory;
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/Chunk.cs b/source/game/planets/Chunk.cs
new file mode 100644
index 0000000..5fc7618
--- /dev/null
+++ b/source/game/planets/Chunk.cs
@@ -0,0 +1,158 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Celesteia.Resources;
+using Celesteia.Graphics;
+using System;
+using Celesteia.Resources.Sprites;
+using Celesteia.Game.Components.Items;
+
+namespace Celesteia.Game.Planets {
+ public class Chunk {
+ public const int CHUNK_SIZE = 16;
+
+ public Point TruePosition { get; private set; }
+ private ChunkVector _position;
+ public ChunkVector Position {
+ get => _position;
+ set { _position = value; TruePosition = _position.Resolve(); }
+ }
+
+ private BlockState[,] foreground;
+ private BlockState[,] background;
+
+ public Chunk(ChunkVector cv) {
+ Position = cv;
+
+ foreground = new BlockState[CHUNK_SIZE, CHUNK_SIZE];
+ background = new BlockState[CHUNK_SIZE, CHUNK_SIZE];
+ }
+
+ public BlockState GetForeground(int x, int y) => foreground[x, y];
+ public BlockState GetBackground(int x, int y) => background[x, y];
+
+ public void SetForeground(int x, int y, byte id) {
+ foreground[x, y].BlockID = id;
+ foreground[x, y].BreakProgress = 0;
+ UpdateDraws(x, y);
+ }
+
+ public void SetBackground(int x, int y, byte id) {
+ background[x, y].BlockID = id;
+ background[x, y].BreakProgress = 0;
+ UpdateDraws(x, y);
+ }
+
+ public void UpdateDraws(int x, int y) {
+ foreground[x, y].Draw = foreground[x, y].HasFrames();
+ background[x, y].Draw = background[x, y].HasFrames() && foreground[x, y].Translucent;
+ }
+
+ private NamespacedKey? dropKey;
+ public void AddBreakProgress(int x, int y, int power, bool wall, out ItemStack drops) {
+ dropKey = null;
+ drops = null;
+
+ if (wall) {
+ background[x, y].BreakProgress += power;
+ if (background[x, y].BreakProgress > background[x, y].Type.Strength) {
+ dropKey = background[x, y].Type.DropKey;
+ SetBackground(x, y, 0);
+ }
+ } else {
+ foreground[x, y].BreakProgress += power;
+ if (foreground[x, y].BreakProgress > foreground[x, y].Type.Strength) {
+ dropKey = foreground[x, y].Type.DropKey;
+ SetForeground(x, y, 0);
+ }
+ }
+
+ if (dropKey.HasValue) drops = new ItemStack(dropKey.Value, 1);
+ }
+
+ public void Draw(GameTime gameTime, SpriteBatch spriteBatch) {
+ for (int i = 0; i < CHUNK_SIZE; i++) {
+ for (int j = 0; j < CHUNK_SIZE; j++) {
+ DrawAllAt(i, j, gameTime, spriteBatch);
+ }
+ }
+ }
+
+ Vector2 v;
+ private void DrawAllAt(int x, int y, GameTime gameTime, SpriteBatch spriteBatch) {
+ v.X = TruePosition.X + x;
+ v.Y = TruePosition.Y + y;
+ v.Floor();
+
+ if (background[x, y].Draw) {
+ DrawWallTile(background[x, y].Frames.GetFrame(0), spriteBatch, v);
+ if (background[x, y].BreakProgress > 0) DrawWallTile(ResourceManager.Blocks.BreakAnimation.GetProgressFrame(
+ // Background block breaking progress.
+ background[x, y].BreakProgress / (float) background[x, y].Type.Strength
+ ), spriteBatch, v);
+ }
+ if (foreground[x, y].Draw) {
+ DrawTile(foreground[x, y].Frames.GetFrame(0), spriteBatch, v);
+ if (foreground[x, y].BreakProgress > 0) DrawTile(ResourceManager.Blocks.BreakAnimation.GetProgressFrame(
+ // Foreground block breaking progress.
+ foreground[x, y].BreakProgress / (float) foreground[x, y].Type.Strength
+ ), spriteBatch, v);
+ }
+ }
+
+ public void DrawTile(BlockFrame frame, SpriteBatch spriteBatch, Vector2 v) {
+ frame.Draw(0, spriteBatch, v, Color.White, 0.4f);
+ }
+
+ public void DrawWallTile(BlockFrame frame, SpriteBatch spriteBatch, Vector2 v) {
+ frame.Draw(0, spriteBatch, v, Color.DarkGray, 0.5f);
+ }
+ }
+
+ public struct ChunkVector {
+ public int X;
+ public int Y;
+
+ public ChunkVector(int x, int y) {
+ X = x;
+ Y = y;
+ }
+
+ public static ChunkVector FromVector2(Vector2 vector)
+ {
+ return new ChunkVector(
+ (int)MathF.Floor(vector.X / Chunk.CHUNK_SIZE),
+ (int)MathF.Floor(vector.Y / Chunk.CHUNK_SIZE)
+ );
+ }
+
+ public static ChunkVector FromPoint(Point point)
+ => new ChunkVector(point.X / Chunk.CHUNK_SIZE, point.Y/ Chunk.CHUNK_SIZE);
+
+ public Point Resolve() {
+ return new Point(X * Chunk.CHUNK_SIZE, Y * Chunk.CHUNK_SIZE);
+ }
+
+ public static int Distance(ChunkVector a, ChunkVector b) {
+ return MathHelper.Max(Math.Abs(b.X - a.X), Math.Abs(b.Y - a.Y));
+ }
+
+ public static bool operator ==(ChunkVector a, ChunkVector b) {
+ return a.Equals(b);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is ChunkVector) return ((ChunkVector)obj).X == this.X && ((ChunkVector)obj).Y == this.Y;
+ return false;
+ }
+
+ public static bool operator !=(ChunkVector a, ChunkVector b) {
+ return !a.Equals(b);
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/ChunkMap.cs b/source/game/planets/ChunkMap.cs
new file mode 100644
index 0000000..234d034
--- /dev/null
+++ b/source/game/planets/ChunkMap.cs
@@ -0,0 +1,99 @@
+using System;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Planets.Generation;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+
+namespace Celesteia.Game.Planets {
+ public class ChunkMap {
+ public int Width, Height;
+ public int BlockWidth => Width * Chunk.CHUNK_SIZE;
+ public int BlockHeight => Height * Chunk.CHUNK_SIZE;
+
+ public Chunk[,] Map;
+
+ public ChunkMap(int w, int h) {
+ Width = w;
+ Height = h;
+
+ Map = new Chunk[w, h];
+ }
+
+ public Chunk GetChunk(int chunkX, int chunkY) => ChunkIsInMap(chunkX, chunkY) ? Map[chunkX, chunkY] : null;
+ public Chunk GetChunk(ChunkVector cv) => GetChunk(cv.X, cv.Y);
+ public Chunk GetChunkAtCoordinates(int x, int y) => GetChunk(x / Chunk.CHUNK_SIZE, y / Chunk.CHUNK_SIZE);
+ public Chunk GetChunkAtPoint(Point point) => GetChunkAtCoordinates(point.X, point.Y);
+
+ // BACKGROUND MANAGEMENT
+ public BlockState GetBackground(int blockX, int blockY) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) return c.GetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE);
+
+ return BlockState.None;
+ }
+ public BlockState GetBackground(Point point) => GetBackground(point.X, point.Y);
+
+ public void SetBackgroundID(int blockX, int blockY, byte id) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.SetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, id);
+ }
+ public void SetBackgroundID(Point point, byte id) => SetBackgroundID(point.X, point.Y, id);
+
+ public void AddBackgroundBreakProgress(int blockX, int blockY, int power, out ItemStack drops) {
+ drops = null;
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.AddBreakProgress(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, power, true, out drops);
+ }
+ public void AddBackgroundBreakProgress(Point point, int power, out ItemStack drops) => AddBackgroundBreakProgress(point.X, point.Y, power, out drops);
+
+ // FOREGROUND MANAGEMENT
+ public BlockState GetForeground(int blockX, int blockY) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) return c.GetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE);
+
+ return BlockState.None;
+ }
+ public BlockState GetForeground(Point point) => GetForeground(point.X, point.Y);
+
+ public void SetForegroundID(int blockX, int blockY, byte id) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.SetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, id);
+ }
+ public void SetForegroundID(Point point, byte id) => SetForegroundID(point.X, point.Y, id);
+
+ public void AddForegroundBreakProgress(int blockX, int blockY, int power, out ItemStack drops) {
+ drops = null;
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ if (c != null) c.AddBreakProgress(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, power, false, out drops);
+ }
+ public void AddForegroundBreakProgress(Point point, int power, out ItemStack drops) => AddForegroundBreakProgress(point.X, point.Y, power, out drops);
+
+ // FOR ADJACENCY CHECKS
+ public bool GetAny(int blockX, int blockY) {
+ Chunk c = GetChunkAtCoordinates(blockX, blockY);
+ return c != null && (
+ !c.GetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE).Empty ||
+ !c.GetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE).Empty
+ );
+ }
+
+ // COLLISION CHECKS
+
+ public RectangleF? TestBoundingBox(int x, int y, RectangleF? box) {
+ if (!box.HasValue) return null;
+
+ return new RectangleF(
+ x, y,
+ box.Value.Width, box.Value.Height
+ );
+ }
+ public RectangleF? TestBoundingBox(int x, int y) => TestBoundingBox(x, y, GetForeground(x, y).Type.BoundingBox);
+
+ // CHUNK IN MAP CHECKS
+ public bool ChunkIsInMap(int chunkX, int chunkY) => !(chunkX < 0 || chunkY < 0 || chunkX >= Width || chunkY >= Height);
+ public bool ChunkIsInMap(ChunkVector cv) => ChunkIsInMap(cv.X, cv.Y);
+
+ public virtual Vector2 GetSpawnpoint() => Vector2.Zero;
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/GeneratedPlanet.cs b/source/game/planets/GeneratedPlanet.cs
new file mode 100644
index 0000000..c89a6c8
--- /dev/null
+++ b/source/game/planets/GeneratedPlanet.cs
@@ -0,0 +1,35 @@
+using System;
+using Celesteia.Game.Planets.Generation;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Planets {
+ public class GeneratedPlanet : ChunkMap
+ {
+ public int Seed { get; private set; }
+
+ private IChunkProvider _provider;
+
+ public GeneratedPlanet(int w, int h, int? seed = null) : base(w, h)
+ {
+ Seed = seed.HasValue ? seed.Value : (int) System.DateTime.Now.Ticks;
+ }
+
+ public void Generate(IChunkProvider provider, Action<string> progressReport = null) {
+ Action<string> doProgressReport = (s) => { if (progressReport != null) progressReport(s); };
+
+ _provider = provider;
+ doProgressReport("Generating chunks...");
+
+ ChunkVector _cv;
+ for (_cv.X = 0; _cv.X < Width; _cv.X++)
+ for (_cv.Y = 0; _cv.Y < Height; _cv.Y++)
+ provider.ProvideChunk(Map[_cv.X, _cv.Y] = new Chunk(_cv));
+
+ provider.GenerateStructures(doProgressReport);
+
+ doProgressReport("Planet generated.");
+ }
+
+ public override Vector2 GetSpawnpoint() => _provider.GetSpawnpoint();
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/generation/IWorldGenerator.cs b/source/game/planets/generation/IWorldGenerator.cs
new file mode 100644
index 0000000..92b528b
--- /dev/null
+++ b/source/game/planets/generation/IWorldGenerator.cs
@@ -0,0 +1,18 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Game.Planets.Generation {
+ public interface IChunkProvider {
+ // Provide a chunk's tile map.
+ public void ProvideChunk(Chunk c);
+
+ // Get the natural block and wall at X and Y.
+ public byte[] GetNaturalBlocks(int x, int y);
+
+ // Get a safe spot to spawn the player.
+ public Vector2 GetSpawnpoint();
+
+ // Generate various structures in the world.
+ public void GenerateStructures(Action<string> progressReport = null);
+ }
+} \ No newline at end of file
diff --git a/source/game/planets/generation/TerranPlanetGenerator.cs b/source/game/planets/generation/TerranPlanetGenerator.cs
new file mode 100644
index 0000000..9507011
--- /dev/null
+++ b/source/game/planets/generation/TerranPlanetGenerator.cs
@@ -0,0 +1,275 @@
+using System;
+using Microsoft.Xna.Framework;
+using Celesteia.Resources;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Celesteia.Game.Planets.Generation {
+ public class TerranPlanetGenerator : IChunkProvider {
+ private ChunkMap _chunkMap;
+ private int _seed;
+ private FastNoiseLite _noise;
+
+ public TerranPlanetGenerator(GeneratedPlanet chunkMap) {
+ _chunkMap = chunkMap;
+ _noise = new FastNoiseLite(_seed = chunkMap.Seed);
+
+ LoadBlockIndices();
+ }
+
+ private byte top;
+ private byte soil;
+ private byte stone;
+ private byte deepstone;
+ private byte log;
+ private byte leaves;
+ private byte planks;
+ private byte coal_ore;
+ private byte copper_ore;
+ private byte iron_ore;
+ private byte[] foliage;
+ private byte landing_floor;
+ private byte cc_base;
+ private byte cc_frame;
+ private void LoadBlockIndices() {
+ top = ResourceManager.Blocks.GetResource(NamespacedKey.Base("grown_soil")).GetID();
+ soil = ResourceManager.Blocks.GetResource(NamespacedKey.Base("soil")).GetID();
+ stone = ResourceManager.Blocks.GetResource(NamespacedKey.Base("stone")).GetID();
+ deepstone = ResourceManager.Blocks.GetResource(NamespacedKey.Base("deepstone")).GetID();
+ log = ResourceManager.Blocks.GetResource(NamespacedKey.Base("log")).GetID();
+ leaves = ResourceManager.Blocks.GetResource(NamespacedKey.Base("leaves")).GetID();
+ planks = ResourceManager.Blocks.GetResource(NamespacedKey.Base("wooden_planks")).GetID();
+ coal_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("coal_ore")).GetID();
+ copper_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("copper_ore")).GetID();
+ iron_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("iron_ore")).GetID();
+
+ foliage = new byte[5] {
+ 0,
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("grass")).GetID(),
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("blue_flower")).GetID(),
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("red_flower")).GetID(),
+ ResourceManager.Blocks.GetResource(NamespacedKey.Base("violet_flower")).GetID()
+ };
+
+ landing_floor = ResourceManager.Blocks.GetResource(NamespacedKey.Base("scorched_soil")).GetID();
+ cc_base = ResourceManager.Blocks.GetResource(NamespacedKey.Base("crashed_capsule_base")).GetID();
+ cc_frame = ResourceManager.Blocks.GetResource(NamespacedKey.Base("crashed_capsule_frame")).GetID();
+ }
+
+ public void ProvideChunk(Chunk c) {
+ byte[] natural;
+ for (int i = 0; i < Chunk.CHUNK_SIZE; i++)
+ for (int j = 0; j < Chunk.CHUNK_SIZE; j++) {
+ natural = GetNaturalBlocks(c.TruePosition.X + i, c.TruePosition.Y + j);
+ c.SetForeground(i, j, natural[0]);
+ c.SetBackground(i, j, natural[1]);
+ }
+ }
+
+ public byte[] GetNaturalBlocks(int x, int y) => ThirdPass(x, y, SecondPass(x, y, FirstPass(x, y)));
+
+ public void GenerateStructures(Action<string> progressReport) {
+ Random rand = new Random(_seed);
+
+ progressReport("Planting trees...");
+ GenerateTrees(rand);
+ progressReport("Abandoning houses...");
+ GenerateAbandonedHomes(rand);
+ progressReport("Planting foliage...");
+ GenerateFoliage(rand);
+ progressReport("Landing light...");
+ GenerateLanding();
+ }
+
+ public Vector2 GetSpawnpoint()
+ {
+ float x;
+ return new Vector2(
+ x = MathF.Floor(_chunkMap.BlockWidth / 2f) + 0.5f,
+ (_chunkMap.BlockHeight) - GetHeightValue((int)MathF.Floor(x)) - 2f
+ );
+ }
+
+ public byte[] FirstPass(int x, int y) {
+ if (y > _chunkMap.BlockHeight - 5) return new byte[2] { deepstone, deepstone };
+
+ byte[] values = new byte[2] { 0, 0 };
+
+ int h = GetHeightValue(x);
+
+ if (_chunkMap.BlockHeight - y <= h) {
+ if (_chunkMap.BlockHeight - y == h) { values[0] = top; values[1] = soil; }
+ else if (_chunkMap.BlockHeight - y >= h - 3) { values[0] = soil; values[1] = soil; }
+ else { values[0] = stone; values[1] = stone; }
+ }
+
+ return values;
+ }
+ public byte[] SecondPass(int x, int y, byte[] values) {
+ float threshold = 0.667f;
+
+ if (values[0] == 0 || values[0] == deepstone) return values;
+ if (values[0] == soil || values[0] == top) threshold += .2f;
+
+ float c = GetCaveValue(x, y);
+
+ if (c > threshold) values[0] = 0;
+
+ return values;
+ }
+
+ public byte[] ThirdPass(int x, int y, byte[] values) {
+ if (values[0] != stone) return values;
+
+ float coalValue = GetOreValue(x, y, 498538f, 985898f);
+ if (coalValue > 0.95f) values[0] = coal_ore;
+ else {
+ float copperValue = GetOreValue(x, y, 3089279f, 579486f);
+ if (copperValue > 0.95f) values[0] = copper_ore;
+
+ else
+ {
+ float ironValue = GetOreValue(x, y, 243984f, 223957f);
+ if (ironValue > 0.95f) values[0] = iron_ore;
+ }
+ }
+
+ return values;
+ }
+
+ private int defaultOffset => _chunkMap.BlockHeight / 3;
+ public int GetHeightValue(int x) => (int)Math.Round((_noise.GetNoise(x / 1f, 0f) * 24f) + defaultOffset);
+ public float GetCaveValue(int x, int y) => _noise.GetNoise(x / 0.6f, y / 0.7f);
+ public float GetOreValue(int x, int y, float offsetX, float offsetY) => (_noise.GetNoise((x + offsetX) * 5f, (y + offsetY) * 5f) + 1) / 2f;
+
+ private int blocksBetweenTrees = 5;
+ private int treeGrowthSteps = 15;
+ public void GenerateTrees(Random rand) {
+ int j = 0;
+ int randomNumber = 0;
+ int lastTree = 0;
+ for (int i = 0; i < _chunkMap.BlockWidth; i++) {
+ j = _chunkMap.BlockHeight - GetHeightValue(i);
+
+ if (MathF.Abs(i - GetSpawnpoint().X) < 10f) continue; // Don't grow trees too close to spawn.
+ if (i < 10 || i > _chunkMap.BlockWidth - 10) continue; // Don't grow trees too close to world borders.
+ if (_chunkMap.GetForeground(i, j).BlockID != top) continue; // Only grow trees on grass.
+ if (i - lastTree < blocksBetweenTrees) continue; // Force a certain number of blocks between trees.
+
+ lastTree = i;
+
+ randomNumber = rand.Next(0, 6);
+
+ if (randomNumber == 1) GrowTreeRecursively(i, j - 1, treeGrowthSteps - rand.Next(0, 7), rand, false);
+ }
+ }
+
+ public void GrowTreeRecursively(int x, int y, int steps, Random rand, bool branch) {
+ if (steps == 0) {
+ for (int i = -2; i <= 2; i++)
+ for (int j = -2; j <= 2; j++) {
+ if (_chunkMap.GetForeground(x + i, y + j).Empty) _chunkMap.SetForegroundID(x + i, y + j, leaves);
+ }
+ return;
+ }
+
+ if (!_chunkMap.GetForeground(x, y).Empty) return;
+
+ _chunkMap.SetForegroundID(x, y, log);
+
+ if (!branch) GrowTreeRecursively(x, y - 1, steps - 1, rand, false); // Grow upwards.
+ if (rand.Next(0, 6) > steps) GrowTreeRecursively(x - 1, y, steps - 1, rand, true); // Grow to the left.
+ if (rand.Next(0, 6) > steps) GrowTreeRecursively(x + 1, y, steps - 1, rand, true); // Grow to the right.
+ }
+
+ private int blocksBetweenHomes = 150;
+ public void GenerateAbandonedHomes(Random rand) {
+ int j = 0;
+ int randomNumber = 0;
+ int lastHome = 0;
+ for (int i = 0; i < _chunkMap.BlockWidth; i++) {
+ j = _chunkMap.BlockHeight - GetHeightValue(i);
+
+ if (MathF.Abs(i - GetSpawnpoint().X) < 10f) continue; // Don't grow trees too close to spawn.
+ if (i < 10 || i > _chunkMap.BlockWidth - 10) continue; // Don't grow trees too close to world borders.
+ if (i - lastHome < blocksBetweenHomes) continue; // Force a certain number of blocks between trees.
+
+ int homeWidth = rand.Next(10, 15);
+ int homeHeight = rand.Next(6, 10);
+ int buryAmount = rand.Next(15, 40);
+
+ j -= homeHeight; // Raise the home to be built on the ground first.
+ j += buryAmount; // Bury the home by a random amount.
+
+ lastHome = i;
+
+ randomNumber = rand.Next(0, 5);
+
+ if (randomNumber == 1) BuildAbandonedHome(i, j, homeWidth, homeHeight, rand);
+ }
+ }
+
+ public void BuildAbandonedHome(int originX, int originY, int homeWidth, int homeHeight, Random rand) {
+ int maxX = originX + homeWidth;
+
+ for (int i = originX; i < maxX; i++) originY = Math.Max(originY, _chunkMap.BlockHeight - GetHeightValue(i));
+ int maxY = originY + homeHeight;
+
+ for (int i = originX; i < maxX; i++)
+ for (int j = originY; j < maxY; j++) {
+ _chunkMap.SetBackgroundID(i, j, planks);
+ _chunkMap.SetForegroundID(i, j, 0);
+
+ // Apply some random decay by skipping tiles at random.
+ if (rand.Next(0, 5) > 3) {
+ continue;
+ }
+
+ if (j == originY || j == maxY - 1) _chunkMap.SetForegroundID(i, j, planks);
+ if (i == originX || i == maxX - 1) _chunkMap.SetForegroundID(i, j, log);
+ }
+ }
+
+ private Dictionary<double, int> foliageDistribution = new Dictionary<double, int> {
+ { 0.3, 0 },
+ { 0.6, 1 },
+ { 0.7, 2 },
+ { 0.85, 4 },
+ { 0.99, 3 },
+ };
+
+ public void GenerateFoliage(Random rand) {
+ int j = 0;
+
+ double randomNumber = 0;
+ int foliageIndex = 0;
+ for (int i = 0; i < _chunkMap.BlockWidth; i++) {
+ j = _chunkMap.BlockHeight - GetHeightValue(i);
+
+ if (_chunkMap.GetForeground(i, j).BlockID != top) continue; // If there is anything but foreground grown soil, continue.
+ if (!_chunkMap.GetForeground(i, j - 1).Empty) continue; // If the foreground is already taken, continue.
+
+ randomNumber = rand.NextDouble();
+ for (int f = 0; f < foliageDistribution.Keys.Count; f++) {
+ if (randomNumber > foliageDistribution.Keys.ElementAt(f)) foliageIndex = foliageDistribution[foliageDistribution.Keys.ElementAt(f)];
+ }
+
+ _chunkMap.SetForegroundID(i, j - 1, foliage[foliageIndex]);
+ }
+ }
+
+ public void GenerateLanding() {
+ int x = GetSpawnpoint().ToPoint().X;
+ int j = _chunkMap.BlockHeight - GetHeightValue(x);
+ for (int i = -1; i <= 1; i++) {
+ _chunkMap.SetForegroundID(x + i, j + 1, soil);
+ _chunkMap.SetForegroundID(x + i, j, landing_floor);
+ for (int h = 1; h <= 3; h++) {
+ _chunkMap.SetForegroundID(x + i, j - h, cc_frame);
+ _chunkMap.SetBackgroundID(x + i, j - h, 0);
+ }
+ }
+ _chunkMap.SetForegroundID(x, j - 1, cc_base);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/CameraRenderSystem.cs b/source/game/systems/CameraRenderSystem.cs
new file mode 100644
index 0000000..2846fa7
--- /dev/null
+++ b/source/game/systems/CameraRenderSystem.cs
@@ -0,0 +1,43 @@
+using Celesteia.Graphics;
+using Celesteia.Resources.Sprites;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems {
+ public class CameraRenderSystem : EntityDrawSystem
+ {
+ private readonly Camera2D _camera;
+ private readonly SpriteBatch _spriteBatch;
+
+ private ComponentMapper<Transform2> transformMapper;
+ private ComponentMapper<EntityFrames> entityFramesMapper;
+
+ public CameraRenderSystem(Camera2D camera, SpriteBatch spriteBatch) : base(Aspect.All(typeof(Transform2), typeof(EntityFrames))) {
+ _camera = camera;
+ _spriteBatch = spriteBatch;
+ }
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ entityFramesMapper = mapperService.GetMapper<EntityFrames>();
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix());
+
+ foreach (int entityId in ActiveEntities) {
+ Transform2 transform = transformMapper.Get(entityId);
+ EntityFrames entityFrames = entityFramesMapper.Get(entityId);
+
+ entityFrames.Draw(0, _spriteBatch, transform.Position, transform.Scale, Color.White);
+ }
+
+ _spriteBatch.End();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/CameraSystem.cs b/source/game/systems/CameraSystem.cs
new file mode 100644
index 0000000..be121a5
--- /dev/null
+++ b/source/game/systems/CameraSystem.cs
@@ -0,0 +1,39 @@
+using System;
+using Celesteia.Game.Components;
+using Celesteia.Game.Input;
+using Celesteia.Graphics;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems {
+ public class CameraSystem : EntityUpdateSystem
+ {
+ private Camera2D _camera;
+ private float smoothing = 128f;
+
+ private ComponentMapper<Transform2> transformMapper;
+
+ public CameraSystem(Camera2D camera) : base(Aspect.All(typeof(Transform2), typeof(CameraFollow)))
+ => _camera = camera;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ => transformMapper = mapperService.GetMapper<Transform2>();
+
+ Vector2 pos;
+ public override void Update(GameTime gameTime)
+ {
+ foreach (int entityId in ActiveEntities) {
+ pos = transformMapper.Get(entityId).Position * smoothing;
+ pos.X = MathF.Round(pos.X) / smoothing;
+ pos.Y = MathF.Round(pos.Y) / smoothing;
+ _camera.Center = pos;
+ break;
+ }
+
+ if (KeyboardHelper.IsDown(Keys.LeftControl)) _camera.Zoom += (int) Math.Clamp(MouseHelper.ScrollDelta, -1f, 1f);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/ChunkMapRenderSystem.cs b/source/game/systems/ChunkMapRenderSystem.cs
new file mode 100644
index 0000000..afb867c
--- /dev/null
+++ b/source/game/systems/ChunkMapRenderSystem.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using Celesteia.Game.Planets;
+using Celesteia.Graphics;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems {
+ public class ChunkMapRenderSystem : IUpdateSystem, IDrawSystem
+ {
+ private readonly Camera2D _camera;
+ private readonly SpriteBatch _spriteBatch;
+ private ChunkVector _lastChunkPos;
+ private ChunkVector _pivotChunkPos => ChunkVector.FromVector2(_camera.Center);
+ public int RenderDistance = 4;
+ private ChunkMap _chunkMap;
+
+ public ChunkMapRenderSystem(Camera2D camera, SpriteBatch spriteBatch, ChunkMap chunkMap) {
+ _camera = camera;
+ _spriteBatch = spriteBatch;
+ _chunkMap = chunkMap;
+ }
+
+ public void Initialize(MonoGame.Extended.Entities.World world) {}
+
+ private ChunkVector _v;
+ private List<ChunkVector> activeChunks = new List<ChunkVector>();
+ public void Update(GameTime gameTime)
+ {
+ if (_lastChunkPos != _pivotChunkPos) {
+ activeChunks.Clear();
+ for (int i = -RenderDistance; i <= RenderDistance; i++) {
+ _v.X = _pivotChunkPos.X + i;
+ for (int j = -RenderDistance; j <= RenderDistance; j++) {
+ _v.Y = _pivotChunkPos.Y + j;
+
+ if (!_chunkMap.ChunkIsInMap(_v)) continue;
+ activeChunks.Add(_v);
+ }
+ }
+
+ _lastChunkPos = _pivotChunkPos;
+ }
+ }
+
+ public void Draw(GameTime gameTime) {
+ _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointWrap, null, null, null, _camera.GetViewMatrix());
+
+ // Draw every chunk in view.
+ foreach (ChunkVector cv in activeChunks) DrawChunk(cv, gameTime, _spriteBatch);
+
+ _spriteBatch.End();
+ }
+
+ private void DrawChunk(ChunkVector cv, GameTime gameTime, SpriteBatch spriteBatch) {
+ Chunk c = _chunkMap.GetChunk(cv);
+
+ if (c != null) c.Draw(gameTime, spriteBatch);
+ }
+
+ public void Dispose() {}
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/EntityDebugSystem.cs b/source/game/systems/EntityDebugSystem.cs
new file mode 100644
index 0000000..9b7914a
--- /dev/null
+++ b/source/game/systems/EntityDebugSystem.cs
@@ -0,0 +1,44 @@
+using Celesteia.Graphics;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems {
+ public class EntityDebugSystem : EntityDrawSystem
+ {
+ private readonly Camera2D _camera;
+ private readonly SpriteBatch _spriteBatch;
+
+ private ComponentMapper<Transform2> transformMapper;
+
+ private SpriteFont _font;
+
+ public EntityDebugSystem(Camera2D camera, SpriteBatch spriteBatch) : base(Aspect.All(typeof(Transform2))) {
+ _camera = camera;
+ _spriteBatch = spriteBatch;
+ }
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+
+ _font = ResourceManager.Fonts.GetFontType("Hobo").Font;
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix());
+
+ foreach (int entityId in ActiveEntities) {
+ Transform2 transform = transformMapper.Get(entityId);
+
+ _spriteBatch.DrawString(_font, transform.Position.ToString(), transform.Position, Color.White, 0f, new Vector2(0.5f, 0.5f), .12f, SpriteEffects.None, 0f);
+ }
+
+ _spriteBatch.End();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/LightingSystem.cs b/source/game/systems/LightingSystem.cs
new file mode 100644
index 0000000..73c6cf8
--- /dev/null
+++ b/source/game/systems/LightingSystem.cs
@@ -0,0 +1,100 @@
+using Celesteia.Graphics;
+using Celesteia.Graphics.Lighting;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Entities.Systems;
+using Celesteia.Resources.Types;
+using Celesteia.Game.Planets;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace Celesteia.Game.Systems {
+ public class LightingSystem : IUpdateSystem, IDrawSystem
+ {
+ private readonly Camera2D _camera;
+ private readonly SpriteBatch _spriteBatch;
+ private readonly ChunkMap _chunkMap;
+
+ public LightingSystem(Camera2D camera, SpriteBatch spriteBatch, ChunkMap chunkMap) {
+ _camera = camera;
+ _spriteBatch = spriteBatch;
+ _chunkMap = chunkMap;
+ }
+ public void Dispose() { }
+
+ private int _lightRenderDistance = 5;
+
+ private Dictionary<byte, BlockLightProperties> lightingDictionary;
+ public void Initialize(MonoGame.Extended.Entities.World world) {
+ int _size = 2 * _lightRenderDistance * Chunk.CHUNK_SIZE;
+
+ _lightMap = new LightMap(_size, _size);
+ _texture = new Texture2D(_spriteBatch.GraphicsDevice, _size, _size);
+
+ lightingDictionary = new Dictionary<byte, BlockLightProperties>();
+ }
+
+ private LightMap _lightMap;
+ private Texture2D _texture;
+
+ private bool drawTexture = false;
+ private Task _lightUpdate;
+ public void Update(GameTime gameTime)
+ {
+ if (_lightUpdate == null || (_lightUpdate != null && _lightUpdate.IsCompleted))
+ _lightUpdate = Task.Factory.StartNew(() => UpdateLight());
+
+ if (drawTexture) UpdateTexture();
+ }
+
+ private Point _position;
+ private void UpdatePosition() {
+ _position = ChunkVector.FromVector2(_camera.Center).Resolve() - new Point(_lightRenderDistance * Chunk.CHUNK_SIZE);
+ }
+
+ private void UpdateTexture() {
+ _drawPosition = _position.ToVector2();
+ _texture.SetData<Color>(_lightMap.GetColors(), 0, _lightMap.GetColorCount());
+ drawTexture = false;
+ }
+
+ private void UpdateLight() {
+ UpdatePosition();
+
+ UpdateEmission();
+ _lightMap.Propagate();
+ _lightMap.CreateColorMap();
+
+ drawTexture = true;
+ }
+
+ private BlockState _block;
+ private void UpdateEmission() {
+ for (int i = 0; i < _lightMap.Width; i++) {
+ for (int j = 0; j < _lightMap.Height; j++) {
+ if (!(_block = _chunkMap.GetForeground(i + _position.X, j + _position.Y)).Empty && _lightMap.AddForeground(i, j, _block.Type.Light)) continue;
+ if (!(_block = _chunkMap.GetBackground(i + _position.X, j + _position.Y)).Empty && _lightMap.AddBackground(i, j, _block.Type.Light)) continue;
+
+ _lightMap.AddLight(i, j, true, LightColor.ambient, 4);
+ }
+ }
+ }
+
+ private BlendState multiply = new BlendState() {
+ ColorBlendFunction = BlendFunction.Add,
+ ColorSourceBlend = Blend.DestinationColor,
+ ColorDestinationBlend = Blend.Zero,
+ };
+
+ private Vector2 _drawPosition;
+ public void Draw(GameTime gameTime)
+ {
+ _spriteBatch.Begin(SpriteSortMode.Immediate, multiply, SamplerState.LinearClamp, null, null, null, _camera.GetViewMatrix());
+
+ _spriteBatch.Draw(_texture, _drawPosition, Color.White);
+
+ _spriteBatch.End();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/LocalPlayerSystem.cs b/source/game/systems/LocalPlayerSystem.cs
new file mode 100644
index 0000000..5c17cb4
--- /dev/null
+++ b/source/game/systems/LocalPlayerSystem.cs
@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using Celesteia.Game.Components;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Components.Physics;
+using Celesteia.Game.Components.Player;
+using Celesteia.Game.Input;
+using Celesteia.Game.Items;
+using Celesteia.Game.Planets;
+using Celesteia.Graphics;
+using Celesteia.GUIs.Game;
+using Celesteia.Resources;
+using Celesteia.Resources.Sprites;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems {
+ public class LocalPlayerSystem : UpdateSystem, IDrawSystem
+ {
+ private GameInstance _game;
+ private GameGUI _gameGui;
+ private Camera2D _camera;
+ private ChunkMap _chunkMap;
+
+ private Entity _player;
+ public Entity Player {
+ get => _player;
+ set {
+ _player = value;
+
+ localPlayer = _player.Get<LocalPlayer>();
+ targetPosition = _player.Get<TargetPosition>();
+ physicsEntity = _player.Get<PhysicsEntity>();
+ frames = _player.Get<EntityFrames>();
+ attributes = _player.Get<EntityAttributes>();
+ input = _player.Get<PlayerInput>();
+ inventory = _player.Get<Inventory>();
+ }
+ }
+ private LocalPlayer localPlayer;
+ private PlayerInput input;
+ private PhysicsEntity physicsEntity;
+ private EntityFrames frames;
+ private EntityAttributes attributes;
+ private TargetPosition targetPosition;
+ private Inventory inventory;
+
+ private SpriteBatch _spriteBatch;
+
+ private BlockFrame _selectionSprite;
+
+ public LocalPlayerSystem(GameInstance game, ChunkMap chunkMap, Camera2D camera, SpriteBatch spriteBatch, GameGUI gameGui) {
+ _game = game;
+ _chunkMap = chunkMap;
+ _camera = camera;
+ _gameGui = gameGui;
+ _spriteBatch = spriteBatch;
+
+ _selectionSprite = ResourceManager.Blocks.Selection.GetFrame(0);
+ }
+
+ private bool IsGameActive => !_gameGui.Paused && (int)_gameGui.State < 1 && _game.IsActive;
+
+ public override void Update(GameTime gameTime)
+ {
+ if (_player == null) return;
+
+ bool clicked = false;
+ UpdateGUI(gameTime, input, out clicked);
+
+ if (IsGameActive) {
+ UpdateSelectedItem();
+
+ UpdateMouse(gameTime, input);
+ UpdateMovement(gameTime, input, physicsEntity, frames, attributes.Attributes, targetPosition);
+ UpdateJump(gameTime, localPlayer, input, physicsEntity, attributes.Attributes);
+
+ if (!clicked) UpdateClick(gameTime, input);
+ } else SelectionColor = Color.Transparent;
+ }
+
+ private static Dictionary<int, int> hotbarMappings = new Dictionary<int, int>() {
+ { (int)Keys.D1, 0 },
+ { (int)Keys.D2, 1 },
+ { (int)Keys.D3, 2 },
+ { (int)Keys.D4, 3 },
+ { (int)Keys.D5, 4 },
+ { (int)Keys.D6, 5 },
+ { (int)Keys.D7, 6 },
+ { (int)Keys.D8, 7 },
+ { (int)Keys.D9, 8 }
+ };
+
+ private void UpdateSelectedItem() {
+ foreach (int keys in hotbarMappings.Keys) {
+ if (KeyboardHelper.Pressed((Keys) keys)) {
+ _gameGui.HotbarSelection = hotbarMappings[keys];
+ }
+ }
+
+ if (!KeyboardHelper.IsDown(Keys.LeftControl) && MouseHelper.ScrollDelta != 0f) {
+ int change = (int) -Math.Clamp(MouseHelper.ScrollDelta, -1f, 1f);
+ int selection = _gameGui.HotbarSelection;
+
+ selection += change;
+
+ if (selection < 0) selection = _gameGui.HotbarSlots - 1;
+ if (selection >= _gameGui.HotbarSlots) selection = 0;
+
+ _gameGui.HotbarSelection = selection;
+ }
+ }
+
+ bool _inventoryPress;
+ bool _craftingPress;
+ bool _pausePress;
+ private void UpdateGUI(GameTime gameTime, PlayerInput input, out bool clicked) {
+ _inventoryPress = input.Inventory.Poll();
+ _craftingPress = input.Crafting.Poll();
+ _pausePress = input.Pause.Poll();
+
+ if (_inventoryPress || _craftingPress || _pausePress) {
+ switch (_gameGui.State) {
+ case InventoryScreenState.Closed:
+ if (_craftingPress) _gameGui.State = InventoryScreenState.Crafting;
+ else if (_inventoryPress) _gameGui.State = InventoryScreenState.Inventory;
+ else if (_pausePress) _gameGui.TogglePause();
+ break;
+ case InventoryScreenState.Inventory:
+ if (_craftingPress) _gameGui.State = InventoryScreenState.Crafting;
+ else _gameGui.State = InventoryScreenState.Closed;
+ break;
+ case InventoryScreenState.Crafting:
+ _gameGui.State = InventoryScreenState.Closed;
+ break;
+ default: break;
+ }
+ }
+
+ _gameGui.Update(gameTime, out clicked);
+ }
+
+ float h;
+ private bool _moving;
+ private double _startedMoving;
+ private void UpdateMovement(GameTime gameTime, PlayerInput input, PhysicsEntity physicsEntity, EntityFrames frames, EntityAttributes.EntityAttributeMap attributes, TargetPosition targetPosition) {
+ h = input.Horizontal.Poll();
+
+ if (h != 0f) {
+ // Player has started moving, animate.
+ if (!_moving) _startedMoving = gameTime.TotalGameTime.TotalSeconds;
+
+ // Flip sprite according to horizontal movement float.
+ frames.Effects = h < 0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
+ }
+
+ _moving = h != 0f;
+
+ // If the player is moving, change the frame every .25 seconds, else return the standing frame.
+ frames.Frame = _moving ? (int)((gameTime.TotalGameTime.TotalSeconds - _startedMoving) / 0.25) : 0;
+
+ if (h == 0f) return;
+
+ h *= 1f + (input.Run.Poll() ? 1.5f : 0);
+ h *= attributes.Get(EntityAttribute.MovementSpeed);
+ h *= gameTime.GetElapsedSeconds();
+
+ targetPosition.Target.X += h;
+ }
+
+ private void UpdateJump(GameTime gameTime, LocalPlayer localPlayer, PlayerInput input, PhysicsEntity physicsEntity, EntityAttributes.EntityAttributeMap attributes)
+ {
+ if (localPlayer.JumpRemaining > 0f) {
+ if (input.Jump.Poll()) {
+ physicsEntity.SetVelocity(physicsEntity.Velocity.X, -attributes.Get(EntityAttribute.JumpForce));
+ localPlayer.JumpRemaining -= gameTime.GetElapsedSeconds();
+ }
+ } else if (physicsEntity.CollidingDown) localPlayer.JumpRemaining = attributes.Get(EntityAttribute.JumpFuel);
+ }
+
+ private Point? Selection;
+ private BlockState SelectedBlock;
+ private Color SelectionColor;
+ public void SetSelection(Point? selection) {
+ if (selection == Selection) return;
+
+ Selection = selection;
+ if (!selection.HasValue) {
+ SelectionColor = Color.Transparent;
+ return;
+ }
+
+ SelectedBlock = _chunkMap.GetForeground(Selection.Value);
+ if (!SelectedBlock.Draw) SelectedBlock = _chunkMap.GetBackground(Selection.Value);
+
+ SelectionColor = (SelectedBlock.Type.Strength >= 0 ? Color.White : Color.Black);
+ }
+
+ Vector2 pointV = Vector2.Zero;
+ Point point = Point.Zero;
+ private void UpdateMouse(GameTime gameTime, PlayerInput input) {
+ pointV = _camera.ScreenToWorld(MouseHelper.Position);
+ pointV.Floor();
+ point = pointV.ToPoint();
+
+ SetSelection(point);
+ }
+
+
+ bool success = false;
+ ItemStack stack = null;
+ IItemActions actions = null;
+ private void UpdateClick(GameTime gameTime, PlayerInput input) {
+ if (!input.PrimaryUse.Poll() && !input.SecondaryUse.Poll()) return;
+ if (_gameGui.GetSelectedItem() == null) return;
+
+ stack = _gameGui.GetSelectedItem();
+ if(stack.Type == null || stack.Type.Actions == null) return;
+
+ actions = stack.Type.Actions;
+
+ if (input.PrimaryUse.Poll()) success = actions.Primary(gameTime, _chunkMap, point, _player);
+ if (input.SecondaryUse.Poll()) success = stack.Type.Actions.Secondary(gameTime, _chunkMap, point, _player);
+
+ if (success && stack.Type.ConsumeOnUse) stack.Amount -= 1;
+
+ inventory.AssertAmounts();
+ }
+
+ public void Draw(GameTime gameTime)
+ {
+ if (!UIReferences.GUIEnabled) return;
+
+ _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix());
+
+ _selectionSprite.Draw(0, _spriteBatch, pointV, SelectionColor);
+
+ _spriteBatch.End();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/TargetPositionSystem.cs b/source/game/systems/TargetPositionSystem.cs
new file mode 100644
index 0000000..7089bc1
--- /dev/null
+++ b/source/game/systems/TargetPositionSystem.cs
@@ -0,0 +1,37 @@
+using Celesteia.Game.Components;
+using Celesteia.Game.Planets;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems {
+ public class TargetPositionSystem : EntityUpdateSystem {
+ private ChunkMap _chunkMap;
+
+ private ComponentMapper<Transform2> transformMapper;
+ private ComponentMapper<TargetPosition> targetPositionMapper;
+
+ public TargetPositionSystem(ChunkMap chunkMap) : base(Aspect.All(typeof(Transform2), typeof(TargetPosition)))
+ => _chunkMap = chunkMap;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ targetPositionMapper = mapperService.GetMapper<TargetPosition>();
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ foreach (int entityId in ActiveEntities) {
+ TargetPosition targetPosition = targetPositionMapper.Get(entityId);
+ Transform2 transform = transformMapper.Get(entityId);
+
+ if (targetPosition.Target.X < 0 || targetPosition.Target.X > _chunkMap.BlockWidth)
+ targetPosition.Target.X = MathHelper.Clamp(targetPosition.Target.X, 0f, _chunkMap.BlockWidth);
+
+ transform.Position = targetPosition.Target;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/mainmenu/MainMenuBackgroundSystem.cs b/source/game/systems/mainmenu/MainMenuBackgroundSystem.cs
new file mode 100644
index 0000000..e4d5e28
--- /dev/null
+++ b/source/game/systems/mainmenu/MainMenuBackgroundSystem.cs
@@ -0,0 +1,33 @@
+using Celesteia.Game.Components.Skybox;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems.MainMenu {
+ public class MainMenuBackgroundSystem : EntityUpdateSystem
+ {
+ private ComponentMapper<Transform2> transformMapper;
+ private ComponentMapper<SkyboxRotateZ> rotatorMapper;
+
+ public MainMenuBackgroundSystem() : base(Aspect.All(typeof(Transform2), typeof(SkyboxRotateZ))) {}
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ rotatorMapper = mapperService.GetMapper<SkyboxRotateZ>();
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ foreach (int entityId in ActiveEntities) {
+ SkyboxRotateZ rotator = rotatorMapper.Get(entityId);
+ Transform2 transform = transformMapper.Get(entityId);
+
+ rotator.Current += rotator.Magnitude * (gameTime.GetElapsedSeconds() / 1000f) * 20f;
+
+ transform.Rotation = rotator.Current;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/mainmenu/MainMenuRenderSystem.cs b/source/game/systems/mainmenu/MainMenuRenderSystem.cs
new file mode 100644
index 0000000..ada6a77
--- /dev/null
+++ b/source/game/systems/mainmenu/MainMenuRenderSystem.cs
@@ -0,0 +1,43 @@
+using Celesteia.Graphics;
+using Celesteia.Resources.Sprites;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems.MainMenu {
+ public class MainMenuRenderSystem : EntityDrawSystem
+ {
+ private ComponentMapper<Transform2> transformMapper;
+ private ComponentMapper<SkyboxPortionFrames> framesMapper;
+
+ private Camera2D _camera;
+ private SpriteBatch _spriteBatch;
+
+ public MainMenuRenderSystem(Camera2D camera, SpriteBatch spriteBatch) : base(Aspect.All(typeof(Transform2), typeof(SkyboxPortionFrames))) {
+ _camera = camera;
+ _spriteBatch = spriteBatch;
+ }
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ framesMapper = mapperService.GetMapper<SkyboxPortionFrames>();
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ _spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.PointClamp, null, RasterizerState.CullNone, null, _camera.GetViewMatrix());
+
+ foreach (int entityId in ActiveEntities) {
+ SkyboxPortionFrames frames = framesMapper.Get(entityId);
+ Transform2 transform = transformMapper.Get(entityId);
+
+ frames.Draw(0, _spriteBatch, transform.Position, transform.Rotation, transform.Scale);
+ }
+
+ _spriteBatch.End();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/physics/PhysicsCollisionDebugSystem.cs b/source/game/systems/physics/PhysicsCollisionDebugSystem.cs
new file mode 100644
index 0000000..f605c30
--- /dev/null
+++ b/source/game/systems/physics/PhysicsCollisionDebugSystem.cs
@@ -0,0 +1,61 @@
+using Celesteia.Game.Components.Physics;
+using Celesteia.Game.Planets;
+using Celesteia.Graphics;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems.Physics {
+ public class PhysicsCollisionDebugSystem : EntityDrawSystem
+ {
+ private readonly Camera2D _camera;
+ private readonly SpriteBatch _spriteBatch;
+ private readonly ChunkMap _chunkMap;
+
+ private ComponentMapper<Transform2> transformMapper;
+ private ComponentMapper<CollisionBox> collisionBoxMapper;
+
+ public PhysicsCollisionDebugSystem(Camera2D camera, SpriteBatch spriteBatch, ChunkMap chunkMap) : base(Aspect.All(typeof(Transform2), typeof(CollisionBox))) {
+ _camera = camera;
+ _spriteBatch = spriteBatch;
+ _chunkMap = chunkMap;
+ }
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ collisionBoxMapper = mapperService.GetMapper<CollisionBox>();
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ if (!GameInstance.DebugMode) return;
+
+ _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix());
+
+ foreach (int entityId in ActiveEntities) {
+ Rectangle box = collisionBoxMapper.Get(entityId).Rounded;
+
+ int minX = box.X;
+ int maxX = box.X + box.Width;
+
+ int minY = box.Y;
+ int maxY = box.Y + box.Height;
+
+ for (int i = minX; i < maxX; i++)
+ for (int j = minY; j < maxY; j++) {
+ RectangleF? blockBox = _chunkMap.TestBoundingBox(i, j);
+ if (blockBox.HasValue) {
+ _spriteBatch.DrawRectangle(new RectangleF(i, j, blockBox.Value.Width, blockBox.Value.Height), Color.Red, .05f, 0f);
+ } else {
+ _spriteBatch.DrawRectangle(new RectangleF(i, j, 1f, 1f), Color.Green, .05f, 0f);
+ }
+ }
+ }
+
+ _spriteBatch.End();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/physics/PhysicsSystem.cs b/source/game/systems/physics/PhysicsSystem.cs
new file mode 100644
index 0000000..3963cc1
--- /dev/null
+++ b/source/game/systems/physics/PhysicsSystem.cs
@@ -0,0 +1,42 @@
+using Celesteia.Game.Components;
+using Celesteia.Game.Components.Physics;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems.Physics {
+ public class PhysicsSystem : EntityUpdateSystem {
+ public const float GRAVITY_CONSTANT = 9.7f;
+
+ public PhysicsSystem() : base(Aspect.All(typeof(PhysicsEntity), typeof(TargetPosition))) {}
+
+ private ComponentMapper<TargetPosition> targetPositionMapper;
+ private ComponentMapper<PhysicsEntity> physicsEntityMapper;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ targetPositionMapper = mapperService.GetMapper<TargetPosition>();
+ physicsEntityMapper = mapperService.GetMapper<PhysicsEntity>();
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ foreach (int entityId in ActiveEntities) {
+ TargetPosition targetPosition = targetPositionMapper.Get(entityId);
+ PhysicsEntity physicsEntity = physicsEntityMapper.Get(entityId);
+
+ // Apply gravity if applicable
+ if (physicsEntity.Gravity) {
+ if (physicsEntity.CollidingDown && physicsEntity.Velocity.Y > 0f) {
+ physicsEntity.SetVelocity(physicsEntity.Velocity.X, 0.1f);
+ }
+
+ physicsEntity.AddVelocity(0f, physicsEntity.Mass * PhysicsSystem.GRAVITY_CONSTANT * gameTime.GetElapsedSeconds());
+ }
+
+ targetPosition.Target += physicsEntity.Velocity * gameTime.GetElapsedSeconds();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/physics/PhysicsWorldCollisionSystem.cs b/source/game/systems/physics/PhysicsWorldCollisionSystem.cs
new file mode 100644
index 0000000..ba8da22
--- /dev/null
+++ b/source/game/systems/physics/PhysicsWorldCollisionSystem.cs
@@ -0,0 +1,83 @@
+using System;
+using Celesteia.Game.Components;
+using Celesteia.Game.Components.Physics;
+using Celesteia.Game.Planets;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems.Physics {
+ public class PhysicsWorldCollisionSystem : EntityUpdateSystem {
+ private ChunkMap _chunkMap;
+
+ public PhysicsWorldCollisionSystem(ChunkMap chunkMap) : base(Aspect.All(typeof(TargetPosition), typeof(PhysicsEntity), typeof(CollisionBox))) {
+ _chunkMap = chunkMap;
+ }
+
+ private ComponentMapper<Transform2> transformMapper;
+ private ComponentMapper<TargetPosition> targetPositionMapper;
+ private ComponentMapper<PhysicsEntity> physicsEntityMapper;
+ private ComponentMapper<CollisionBox> collisionBoxMapper;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ targetPositionMapper = mapperService.GetMapper<TargetPosition>();
+ physicsEntityMapper = mapperService.GetMapper<PhysicsEntity>();
+ collisionBoxMapper = mapperService.GetMapper<CollisionBox>();
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ foreach (int entityId in ActiveEntities) {
+ Transform2 transform = transformMapper.Get(entityId);
+ TargetPosition targetPosition = targetPositionMapper.Get(entityId);
+ PhysicsEntity physicsEntity = physicsEntityMapper.Get(entityId);
+ CollisionBox collisionBox = collisionBoxMapper.Get(entityId);
+
+ collisionBox.Update(targetPosition.Target);
+
+ int minX = (int)MathF.Floor(collisionBox.Bounds.Center.X - (collisionBox.Bounds.Width / 2f));
+ int maxX = (int)MathF.Ceiling(collisionBox.Bounds.Center.X + (collisionBox.Bounds.Width / 2f));
+
+ int minY = (int)MathF.Floor(collisionBox.Bounds.Center.Y - (collisionBox.Bounds.Height / 2f));
+ int maxY = (int)MathF.Ceiling(collisionBox.Bounds.Center.Y + (collisionBox.Bounds.Height / 2f));
+
+ bool collLeft = false;
+ bool collRight = false;
+ bool collUp = false;
+ bool collDown = false;
+
+ for (int i = minX; i < maxX; i++)
+ for (int j = minY; j < maxY; j++) {
+ RectangleF? blockBox = _chunkMap.TestBoundingBox(i, j);
+ if (blockBox.HasValue) {
+ RectangleF inter = RectangleF.Intersection(collisionBox.Bounds, blockBox.Value);
+
+ if (inter.IsEmpty) continue;
+
+ if (inter.Width < inter.Height) {
+ collLeft = blockBox.Value.Center.X < collisionBox.Bounds.Center.X;
+ collRight = blockBox.Value.Center.X > collisionBox.Bounds.Center.X;
+
+ targetPosition.Target += new Vector2(blockBox.Value.Center.X < collisionBox.Bounds.Center.X ? inter.Width : -inter.Width, 0f);
+ } else {
+ collUp = blockBox.Value.Center.Y < collisionBox.Bounds.Center.Y;
+ collDown = blockBox.Value.Center.Y > collisionBox.Bounds.Center.Y;
+
+ targetPosition.Target += new Vector2(0f, blockBox.Value.Center.Y < collisionBox.Bounds.Center.Y ? inter.Height : -inter.Height);
+ }
+
+ collisionBox.Update(targetPosition.Target);
+ }
+ }
+
+ physicsEntity.CollidingDown = collDown;
+ physicsEntity.CollidingUp = collUp;
+ physicsEntity.CollidingLeft = collLeft;
+ physicsEntity.CollidingRight = collRight;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/game/systems/ui/GameGUIDrawSystem.cs b/source/game/systems/ui/GameGUIDrawSystem.cs
new file mode 100644
index 0000000..0140786
--- /dev/null
+++ b/source/game/systems/ui/GameGUIDrawSystem.cs
@@ -0,0 +1,12 @@
+using Celesteia.GUIs.Game;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities.Systems;
+
+namespace Celesteia.Game.Systems.UI {
+ public class GameGUIDrawSystem : DrawSystem {
+ private GameGUI _gui;
+
+ public GameGUIDrawSystem(GameGUI gui) => _gui = gui;
+ public override void Draw(GameTime gameTime) => _gui.Draw(gameTime);
+ }
+} \ No newline at end of file
diff --git a/source/graphics/Camera2D.cs b/source/graphics/Camera2D.cs
new file mode 100644
index 0000000..1bae764
--- /dev/null
+++ b/source/graphics/Camera2D.cs
@@ -0,0 +1,63 @@
+using System;
+using Celesteia.Resources;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.Graphics {
+ public class Camera2D {
+ private GraphicsDevice _graphicsDevice;
+
+ // Viewport macros.
+ private int ViewportX => _graphicsDevice.Viewport.X;
+ private int ViewportY => _graphicsDevice.Viewport.Y;
+ private int ViewportWidth => _graphicsDevice.Viewport.Width;
+ private int ViewportHeight => _graphicsDevice.Viewport.Height;
+
+ private int _zoom = 0;
+ public int ScaledZoom { get; private set; } = 0;
+ // The zoom value of the camera.
+ public int Zoom {
+ get { return _zoom; }
+ set {
+ _zoom = MathHelper.Clamp(value, 1, 8);
+ ScaledZoom = _zoom * ResourceManager.INVERSE_SPRITE_SCALING;
+ }
+ }
+
+ public Camera2D(GraphicsDevice graphicsDevice) {
+ _graphicsDevice = graphicsDevice;
+ Zoom = 2;
+ }
+
+ // The camera's center.
+ public Vector2 Center = Vector2.Zero;
+
+ private float _rotation;
+ // The rotation applied to the camera.
+ public float Rotation {
+ get { return _rotation; }
+ set { _rotation = value % 360f; }
+ }
+
+ /*
+ Creates a matrix with the following steps:
+ - Create a translation to match (0, 0) to the center point of the camera.
+ - Apply Z rotation.
+ - Scale according to zoom value and inverse sprite scaling.
+ - Always round the viewport width and height to prevent half-pixel rounding issues.
+ */
+ private float maxScale = 0f;
+ public Matrix GetViewMatrix() {
+ maxScale = MathF.Max(MathF.Ceiling(ViewportWidth / 1920f), MathF.Ceiling(ViewportHeight / 1080f));
+ return Matrix.CreateTranslation(-Center.X, -Center.Y, 0f) *
+ Matrix.CreateRotationZ(Rotation) *
+ Matrix.CreateScale(ScaledZoom, ScaledZoom, 1f) *
+ Matrix.CreateScale(maxScale, maxScale, 1f) *
+ Matrix.CreateTranslation(ViewportWidth / 2f, ViewportHeight / 2f, 0f);
+ }
+
+ // Transform the viewport relative mouse position to the inverse view matrix to get the pointer's position in the world.
+ public Vector2 ScreenToWorld(Point point)
+ => Vector2.Transform(new Vector2(point.X - ViewportX, point.Y - ViewportY), Matrix.Invert(GetViewMatrix()));
+ }
+} \ No newline at end of file
diff --git a/source/graphics/GraphicsManager.cs b/source/graphics/GraphicsManager.cs
new file mode 100644
index 0000000..2e285cf
--- /dev/null
+++ b/source/graphics/GraphicsManager.cs
@@ -0,0 +1,65 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.Graphics {
+ public enum FullscreenMode {
+ Windowed, Fullscreen, Borderless
+ }
+
+ public class GraphicsManager : GameComponent {
+ private new GameInstance Game => (GameInstance) base.Game;
+ private GraphicsDeviceManager _manager;
+ public GraphicsManager(GameInstance Game) : base(Game) {
+ _manager = new GraphicsDeviceManager(Game);
+ _manager.PreferHalfPixelOffset = true;
+ }
+
+ private FullscreenMode _screenMode;
+ private bool _useBorderless = false;
+ private Rectangle _resolution = new Rectangle(0, 0, 1280, 720);
+ private Rectangle _lastBounds;
+
+ public FullscreenMode FullScreen {
+ get { return _screenMode; }
+ set { _screenMode = value; ResolveResolution(); }
+ }
+
+ public bool VSync = false;
+
+ public bool IsFullScreen {
+ get { return (_screenMode != FullscreenMode.Windowed); }
+ }
+
+ public Rectangle Resolution {
+ get { return _resolution; }
+ set { _lastBounds = _resolution = value; }
+ }
+
+ public bool MSAA = false;
+
+ private void ResolveResolution() {
+ if (!IsFullScreen) _resolution = _lastBounds;
+ else {
+ _lastBounds = Game.Window.ClientBounds;
+ _resolution = new Rectangle(0, 0, _manager.GraphicsDevice.Adapter.CurrentDisplayMode.Width, _manager.GraphicsDevice.Adapter.CurrentDisplayMode.Height);
+ }
+ }
+
+ public void Apply() {
+ Game.Window.AllowUserResizing = true;
+ _manager.PreferMultiSampling = MSAA;
+ _manager.PreferredBackBufferWidth = _resolution.Width;
+ _manager.PreferredBackBufferHeight = _resolution.Height;
+ _manager.PreferredBackBufferFormat = SurfaceFormat.Color;
+ _manager.HardwareModeSwitch = (_screenMode == FullscreenMode.Borderless);
+ _manager.IsFullScreen = IsFullScreen;
+ _manager.SynchronizeWithVerticalRetrace = VSync;
+ _manager.ApplyChanges();
+ }
+
+ public GraphicsManager ToggleFullScreen() {
+ FullScreen = IsFullScreen ? FullscreenMode.Windowed : (_useBorderless ? FullscreenMode.Borderless : FullscreenMode.Fullscreen);
+ return this;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/graphics/lighting/LightMap.cs b/source/graphics/lighting/LightMap.cs
new file mode 100644
index 0000000..4fb3b9d
--- /dev/null
+++ b/source/graphics/lighting/LightMap.cs
@@ -0,0 +1,139 @@
+using System;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.Graphics.Lighting {
+ public class LightMap {
+ private bool[,] _emit;
+ private LightColor[,] _lightColors;
+ private int[,] _propagation;
+
+ public readonly int Width;
+ public readonly int Height;
+
+ public LightMap(int width, int height) {
+ Width = width;
+ Height = height;
+
+ _emit = new bool[width, height];
+ _lightColors = new LightColor[width, height];
+ _propagation = new int[width, height];
+ }
+
+ public bool AddForeground(int x, int y, BlockLightProperties blockLight) {
+ if (blockLight.Emits) AddLight(x, y, blockLight.Emits, blockLight.Color, blockLight.Propagation);
+ else if (blockLight.Occludes) AddDarkness(x, y);
+
+ return blockLight.Emits || blockLight.Occludes;
+ }
+
+ public bool AddBackground(int x, int y, BlockLightProperties blockLight) {
+ if (blockLight.Occludes) {
+ if (blockLight.Emits) AddLight(x, y, blockLight.Emits, blockLight.Color, blockLight.Propagation);
+ else AddDarkness(x, y);
+ }
+
+ return blockLight.Occludes;
+ }
+
+ public void AddLight(int x, int y, bool emit, LightColor color, int propagation) {
+ if (!InMap(x, y)) return;
+
+ _emit[x, y] = emit;
+ _lightColors[x, y] = color;
+ _propagation[x, y] = propagation;
+ }
+
+ public void AddDarkness(int x, int y) => AddLight(x, y, false, LightColor.black, 0);
+
+ public void Propagate() {
+ for (int x = 0; x < Width; x++)
+ for (int y = 0; y < Height; y++)
+ if (_emit[x, y])
+ PropagateFrom(x, y, _lightColors[x, y], _propagation[x, y]);
+ }
+
+ public bool InMap(int x, int y) => !(x < 0 || x >= Width || y < 0 || y >= Height);
+
+ private float _normalDropoff = 0.7f;
+ private float _diagonalDropoff => _normalDropoff * _normalDropoff;
+ private int lookX;
+ private int lookY;
+ private LightColor _target;
+ private int distance;
+ private void PropagateFrom(int x, int y, LightColor color, int propagation) {
+ for (int i = -propagation; i <= propagation; i++) {
+ lookX = x + i;
+ for (int j = -propagation; j <= propagation; j++) {
+ lookY = y + j;
+
+ if (!InMap(lookX, lookY)) continue;
+ if (_emit[lookX, lookY]) continue;
+
+ distance = Math.Max(Math.Abs(i), Math.Abs(j));
+
+ _target = color * (float)(distance > propagation - 3 ? Math.Pow((i != 0 && j != 0) ? _diagonalDropoff : _normalDropoff, distance - (propagation - 3)) : 1);
+
+ if (!_lightColors[lookX, lookY].Equals(LightColor.black))
+ {
+ _target.R = MathF.Max(_target.R, _lightColors[lookX, lookY].R);
+ _target.G = MathF.Max(_target.G, _lightColors[lookX, lookY].G);
+ _target.B = MathF.Max(_target.B, _lightColors[lookX, lookY].B);
+ }
+
+ _lightColors[lookX, lookY] = _target;
+ }
+ }
+ }
+
+ private Color[] _colorMap;
+ public void CreateColorMap() {
+ _colorMap = new Color[Width * Height];
+
+ for (int y = 0; y < Height; y++)
+ for (int x = 0; x < Width; x++)
+ _colorMap[y * Width + x] = _lightColors[x, y].Color;
+ }
+
+ public Color[] GetColors() => _colorMap;
+ public int GetColorCount() => _colorMap.Length;
+ }
+
+ public struct LightColor {
+ public static LightColor black = new LightColor(0, 0, 0);
+ public static LightColor white = new LightColor(255f, 255f, 255f);
+ public static LightColor ambient = new LightColor(255f, 255f, 255f);
+ public static LightColor cave = new LightColor(40f, 40f, 40f);
+
+ public float R;
+ public float G;
+ public float B;
+
+ public Color Color => new Color(R / 255f, G / 255f, B / 255f);
+
+ public LightColor(float r, float g, float b) {
+ R = Math.Clamp(r, 0, 255f);
+ G = Math.Clamp(g, 0, 255f);
+ B = Math.Clamp(b, 0, 255f);
+ }
+
+ public bool IsCutoff(float cutoff) => R > cutoff || G > cutoff || B > cutoff;
+ public bool Overpowers(LightColor other) => R > other.R || G > other.G || B > other.B;
+ public bool Equals(LightColor other) => R == other.R && G == other.G && B == other.B;
+ public static LightColor FromColor(Color color) => new LightColor(color.R, color.G, color.B);
+
+ public static LightColor operator *(LightColor a, LightColor b) {
+ a.R *= b.R;
+ a.G *= b.G;
+ a.B *= b.B;
+ return a;
+ }
+
+ public static LightColor operator *(LightColor a, float multiplier) {
+ a.R *= multiplier;
+ a.G *= multiplier;
+ a.B *= multiplier;
+ return a;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/lib/FastNoiseLite.cs b/source/lib/FastNoiseLite.cs
new file mode 100644
index 0000000..1518d3a
--- /dev/null
+++ b/source/lib/FastNoiseLite.cs
@@ -0,0 +1,2506 @@
+// MIT License
+//
+// Copyright(c) 2020 Jordan Peck (jordan.me2@gmail.com)
+// Copyright(c) 2020 Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions :
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+// .'',;:cldxkO00KKXXNNWWWNNXKOkxdollcc::::::;:::ccllloooolllllllllooollc:,'... ...........',;cldxkO000Okxdlc::;;;,,;;;::cclllllll
+// ..',;:ldxO0KXXNNNNNNNNXXK0kxdolcc::::::;;;,,,,,,;;;;;;;;;;:::cclllllc:;'.... ...........',;:ldxO0KXXXK0Okxdolc::;;;;::cllodddddo
+// ...',:loxO0KXNNNNNXXKK0Okxdolc::;::::::::;;;,,'''''.....''',;:clllllc:;,'............''''''''',;:loxO0KXNNNNNXK0Okxdollccccllodxxxxxxd
+// ....';:ldkO0KXXXKK00Okxdolcc:;;;;;::cclllcc:;;,''..... ....',;clooddolcc:;;;;,,;;;;;::::;;;;;;:cloxk0KXNWWWWWWNXKK0Okxddoooddxxkkkkkxx
+// .....';:ldxkOOOOOkxxdolcc:;;;,,,;;:cllooooolcc:;'... ..,:codxkkkxddooollloooooooollcc:::::clodkO0KXNWWWWWWNNXK00Okxxxxxxxxkkkkxxx
+// . ....';:cloddddo___________,,,,;;:clooddddoolc:,... ..,:ldx__00OOOkkk___kkkkkkxxdollc::::cclodkO0KXXNNNNNNXXK0OOkxxxxxxxxxxxxddd
+// .......',;:cccc:| |,,,;;:cclooddddoll:;'.. ..';cox| \KKK000| |KK00OOkxdocc___;::clldxxkO0KKKKK00Okkxdddddddddddddddoo
+// .......'',,,,,''| ________|',,;;::cclloooooolc:;'......___:ldk| \KK000| |XKKK0Okxolc| |;;::cclodxxkkkkxxdoolllcclllooodddooooo
+// ''......''''....| | ....'',,,,;;;::cclloooollc:;,''.'| |oxk| \OOO0| |KKK00Oxdoll|___|;;;;;::ccllllllcc::;;,,;;;:cclloooooooo
+// ;;,''.......... | |_____',,;;;____:___cllo________.___| |___| \xkk| |KK_______ool___:::;________;;;_______...'',;;:ccclllloo
+// c:;,''......... | |:::/ ' |lo/ | | \dx| |0/ \d| |cc/ |'/ \......',,;;:ccllo
+// ol:;,'..........| _____|ll/ __ |o/ ______|____ ___| | \o| |/ ___ \| |o/ ______|/ ___ \ .......'',;:clo
+// dlc;,...........| |::clooo| / | |x\___ \KXKKK0| |dol| |\ \| | | | | |d\___ \..| | / / ....',:cl
+// xoc;'... .....'| |llodddd| \__| |_____\ \KKK0O| |lc:| |'\ | |___| | |_____\ \.| |_/___/... ...',;:c
+// dlc;'... ....',;| |oddddddo\ | |Okkx| |::;| |..\ |\ /| | | \ |... ....',;:c
+// ol:,'.......',:c|___|xxxddollc\_____,___|_________/ddoll|___|,,,|___|...\_____|:\ ______/l|___|_________/...\________|'........',;::cc
+// c:;'.......';:codxxkkkkxxolc::;::clodxkOO0OOkkxdollc::;;,,''''',,,,''''''''''',,'''''',;:loxkkOOkxol:;,'''',,;:ccllcc:;,'''''',;::ccll
+// ;,'.......',:codxkOO0OOkxdlc:;,,;;:cldxxkkxxdolc:;;,,''.....'',;;:::;;,,,'''''........,;cldkO0KK0Okdoc::;;::cloodddoolc:;;;;;::ccllooo
+// .........',;:lodxOO0000Okdoc:,,',,;:clloddoolc:;,''.......'',;:clooollc:;;,,''.......',:ldkOKXNNXX0Oxdolllloddxxxxxxdolccccccllooodddd
+// . .....';:cldxkO0000Okxol:;,''',,;::cccc:;,,'.......'',;:cldxxkkxxdolc:;;,'.......';coxOKXNWWWNXKOkxddddxxkkkkkkxdoollllooddxxxxkkk
+// ....',;:codxkO000OOxdoc:;,''',,,;;;;,''.......',,;:clodkO00000Okxolc::;,,''..',;:ldxOKXNWWWNNK0OkkkkkkkkkkkxxddooooodxxkOOOOO000
+// ....',;;clodxkkOOOkkdolc:;,,,,,,,,'..........,;:clodxkO0KKXKK0Okxdolcc::;;,,,;;:codkO0XXNNNNXKK0OOOOOkkkkxxdoollloodxkO0KKKXXXXX
+//
+// VERSION: 1.0.1
+// https://github.com/Auburn/FastNoise
+
+using System;
+using System.Runtime.CompilerServices;
+
+// Switch between using floats or doubles for input position
+using FNLfloat = System.Single;
+//using FNLfloat = System.Double;
+
+public class FastNoiseLite
+{
+ private const short INLINE = 256; // MethodImplOptions.AggressiveInlining;
+ private const short OPTIMISE = 512; // MethodImplOptions.AggressiveOptimization;
+
+ public enum NoiseType
+ {
+ OpenSimplex2,
+ OpenSimplex2S,
+ Cellular,
+ Perlin,
+ ValueCubic,
+ Value
+ };
+
+ public enum RotationType3D
+ {
+ None,
+ ImproveXYPlanes,
+ ImproveXZPlanes
+ };
+
+ public enum FractalType
+ {
+ None,
+ FBm,
+ Ridged,
+ PingPong,
+ DomainWarpProgressive,
+ DomainWarpIndependent
+ };
+
+ public enum CellularDistanceFunction
+ {
+ Euclidean,
+ EuclideanSq,
+ Manhattan,
+ Hybrid
+ };
+
+ public enum CellularReturnType
+ {
+ CellValue,
+ Distance,
+ Distance2,
+ Distance2Add,
+ Distance2Sub,
+ Distance2Mul,
+ Distance2Div
+ };
+
+ public enum DomainWarpType
+ {
+ OpenSimplex2,
+ OpenSimplex2Reduced,
+ BasicGrid
+ };
+
+ private enum TransformType3D
+ {
+ None,
+ ImproveXYPlanes,
+ ImproveXZPlanes,
+ DefaultOpenSimplex2
+ };
+
+ private int mSeed = 1337;
+ private float mFrequency = 0.01f;
+ private NoiseType mNoiseType = NoiseType.OpenSimplex2;
+ private RotationType3D mRotationType3D = RotationType3D.None;
+ private TransformType3D mTransformType3D = TransformType3D.DefaultOpenSimplex2;
+
+ private FractalType mFractalType = FractalType.None;
+ private int mOctaves = 3;
+ private float mLacunarity = 2.0f;
+ private float mGain = 0.5f;
+ private float mWeightedStrength = 0.0f;
+ private float mPingPongStrength = 2.0f;
+
+ private float mFractalBounding = 1 / 1.75f;
+
+ private CellularDistanceFunction mCellularDistanceFunction = CellularDistanceFunction.EuclideanSq;
+ private CellularReturnType mCellularReturnType = CellularReturnType.Distance;
+ private float mCellularJitterModifier = 1.0f;
+
+ private DomainWarpType mDomainWarpType = DomainWarpType.OpenSimplex2;
+ private TransformType3D mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2;
+ private float mDomainWarpAmp = 1.0f;
+
+ /// <summary>
+ /// Create new FastNoise object with optional seed
+ /// </summary>
+ public FastNoiseLite(int seed = 1337)
+ {
+ SetSeed(seed);
+ }
+
+ /// <summary>
+ /// Sets seed used for all noise types
+ /// </summary>
+ /// <remarks>
+ /// Default: 1337
+ /// </remarks>
+ public void SetSeed(int seed) { mSeed = seed; }
+
+ /// <summary>
+ /// Sets frequency for all noise types
+ /// </summary>
+ /// <remarks>
+ /// Default: 0.01
+ /// </remarks>
+ public void SetFrequency(float frequency) { mFrequency = frequency; }
+
+ /// <summary>
+ /// Sets noise algorithm used for GetNoise(...)
+ /// </summary>
+ /// <remarks>
+ /// Default: OpenSimplex2
+ /// </remarks>
+ public void SetNoiseType(NoiseType noiseType)
+ {
+ mNoiseType = noiseType;
+ UpdateTransformType3D();
+ }
+
+ /// <summary>
+ /// Sets domain rotation type for 3D Noise and 3D DomainWarp.
+ /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D
+ /// </summary>
+ /// <remarks>
+ /// Default: None
+ /// </remarks>
+ public void SetRotationType3D(RotationType3D rotationType3D)
+ {
+ mRotationType3D = rotationType3D;
+ UpdateTransformType3D();
+ UpdateWarpTransformType3D();
+ }
+
+ /// <summary>
+ /// Sets method for combining octaves in all fractal noise types
+ /// </summary>
+ /// <remarks>
+ /// Default: None
+ /// Note: FractalType.DomainWarp... only affects DomainWarp(...)
+ /// </remarks>
+ public void SetFractalType(FractalType fractalType) { mFractalType = fractalType; }
+
+ /// <summary>
+ /// Sets octave count for all fractal noise types
+ /// </summary>
+ /// <remarks>
+ /// Default: 3
+ /// </remarks>
+ public void SetFractalOctaves(int octaves)
+ {
+ mOctaves = octaves;
+ CalculateFractalBounding();
+ }
+
+ /// <summary>
+ /// Sets octave lacunarity for all fractal noise types
+ /// </summary>
+ /// <remarks>
+ /// Default: 2.0
+ /// </remarks>
+ public void SetFractalLacunarity(float lacunarity) { mLacunarity = lacunarity; }
+
+ /// <summary>
+ /// Sets octave gain for all fractal noise types
+ /// </summary>
+ /// <remarks>
+ /// Default: 0.5
+ /// </remarks>
+ public void SetFractalGain(float gain)
+ {
+ mGain = gain;
+ CalculateFractalBounding();
+ }
+
+ /// <summary>
+ /// Sets octave weighting for all none DomainWarp fratal types
+ /// </summary>
+ /// <remarks>
+ /// Default: 0.0
+ /// Note: Keep between 0...1 to maintain -1...1 output bounding
+ /// </remarks>
+ public void SetFractalWeightedStrength(float weightedStrength) { mWeightedStrength = weightedStrength; }
+
+ /// <summary>
+ /// Sets strength of the fractal ping pong effect
+ /// </summary>
+ /// <remarks>
+ /// Default: 2.0
+ /// </remarks>
+ public void SetFractalPingPongStrength(float pingPongStrength) { mPingPongStrength = pingPongStrength; }
+
+
+ /// <summary>
+ /// Sets distance function used in cellular noise calculations
+ /// </summary>
+ /// <remarks>
+ /// Default: Distance
+ /// </remarks>
+ public void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { mCellularDistanceFunction = cellularDistanceFunction; }
+
+ /// <summary>
+ /// Sets return type from cellular noise calculations
+ /// </summary>
+ /// <remarks>
+ /// Default: EuclideanSq
+ /// </remarks>
+ public void SetCellularReturnType(CellularReturnType cellularReturnType) { mCellularReturnType = cellularReturnType; }
+
+ /// <summary>
+ /// Sets the maximum distance a cellular point can move from it's grid position
+ /// </summary>
+ /// <remarks>
+ /// Default: 1.0
+ /// Note: Setting this higher than 1 will cause artifacts
+ /// </remarks>
+ public void SetCellularJitter(float cellularJitter) { mCellularJitterModifier = cellularJitter; }
+
+
+ /// <summary>
+ /// Sets the warp algorithm when using DomainWarp(...)
+ /// </summary>
+ /// <remarks>
+ /// Default: OpenSimplex2
+ /// </remarks>
+ public void SetDomainWarpType(DomainWarpType domainWarpType)
+ {
+ mDomainWarpType = domainWarpType;
+ UpdateWarpTransformType3D();
+ }
+
+
+ /// <summary>
+ /// Sets the maximum warp distance from original position when using DomainWarp(...)
+ /// </summary>
+ /// <remarks>
+ /// Default: 1.0
+ /// </remarks>
+ public void SetDomainWarpAmp(float domainWarpAmp) { mDomainWarpAmp = domainWarpAmp; }
+
+
+ /// <summary>
+ /// 2D noise at given position using current settings
+ /// </summary>
+ /// <returns>
+ /// Noise output bounded between -1...1
+ /// </returns>
+ [MethodImpl(OPTIMISE)]
+ public float GetNoise(FNLfloat x, FNLfloat y)
+ {
+ TransformNoiseCoordinate(ref x, ref y);
+
+ switch (mFractalType)
+ {
+ default:
+ return GenNoiseSingle(mSeed, x, y);
+ case FractalType.FBm:
+ return GenFractalFBm(x, y);
+ case FractalType.Ridged:
+ return GenFractalRidged(x, y);
+ case FractalType.PingPong:
+ return GenFractalPingPong(x, y);
+ }
+ }
+
+ /// <summary>
+ /// 3D noise at given position using current settings
+ /// </summary>
+ /// <returns>
+ /// Noise output bounded between -1...1
+ /// </returns>
+ [MethodImpl(OPTIMISE)]
+ public float GetNoise(FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ TransformNoiseCoordinate(ref x, ref y, ref z);
+
+ switch (mFractalType)
+ {
+ default:
+ return GenNoiseSingle(mSeed, x, y, z);
+ case FractalType.FBm:
+ return GenFractalFBm(x, y, z);
+ case FractalType.Ridged:
+ return GenFractalRidged(x, y, z);
+ case FractalType.PingPong:
+ return GenFractalPingPong(x, y, z);
+ }
+ }
+
+
+ /// <summary>
+ /// 2D warps the input position using current domain warp settings
+ /// </summary>
+ /// <example>
+ /// Example usage with GetNoise
+ /// <code>DomainWarp(ref x, ref y)
+ /// noise = GetNoise(x, y)</code>
+ /// </example>
+ [MethodImpl(OPTIMISE)]
+ public void DomainWarp(ref FNLfloat x, ref FNLfloat y)
+ {
+ switch (mFractalType)
+ {
+ default:
+ DomainWarpSingle(ref x, ref y);
+ break;
+ case FractalType.DomainWarpProgressive:
+ DomainWarpFractalProgressive(ref x, ref y);
+ break;
+ case FractalType.DomainWarpIndependent:
+ DomainWarpFractalIndependent(ref x, ref y);
+ break;
+ }
+ }
+
+ /// <summary>
+ /// 3D warps the input position using current domain warp settings
+ /// </summary>
+ /// <example>
+ /// Example usage with GetNoise
+ /// <code>DomainWarp(ref x, ref y, ref z)
+ /// noise = GetNoise(x, y, z)</code>
+ /// </example>
+ [MethodImpl(OPTIMISE)]
+ public void DomainWarp(ref FNLfloat x, ref FNLfloat y, ref FNLfloat z)
+ {
+ switch (mFractalType)
+ {
+ default:
+ DomainWarpSingle(ref x, ref y, ref z);
+ break;
+ case FractalType.DomainWarpProgressive:
+ DomainWarpFractalProgressive(ref x, ref y, ref z);
+ break;
+ case FractalType.DomainWarpIndependent:
+ DomainWarpFractalIndependent(ref x, ref y, ref z);
+ break;
+ }
+ }
+
+
+ private static readonly float[] Gradients2D =
+ {
+ 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f,
+ 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f,
+ 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f,
+ -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f,
+ -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f,
+ -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f,
+ 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f,
+ 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f,
+ 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f,
+ -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f,
+ -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f,
+ -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f,
+ 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f,
+ 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f,
+ 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f,
+ -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f,
+ -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f,
+ -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f,
+ 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f,
+ 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f,
+ 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f,
+ -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f,
+ -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f,
+ -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f,
+ 0.130526192220052f, 0.99144486137381f, 0.38268343236509f, 0.923879532511287f, 0.608761429008721f, 0.793353340291235f, 0.793353340291235f, 0.608761429008721f,
+ 0.923879532511287f, 0.38268343236509f, 0.99144486137381f, 0.130526192220051f, 0.99144486137381f, -0.130526192220051f, 0.923879532511287f, -0.38268343236509f,
+ 0.793353340291235f, -0.60876142900872f, 0.608761429008721f, -0.793353340291235f, 0.38268343236509f, -0.923879532511287f, 0.130526192220052f, -0.99144486137381f,
+ -0.130526192220052f, -0.99144486137381f, -0.38268343236509f, -0.923879532511287f, -0.608761429008721f, -0.793353340291235f, -0.793353340291235f, -0.608761429008721f,
+ -0.923879532511287f, -0.38268343236509f, -0.99144486137381f, -0.130526192220052f, -0.99144486137381f, 0.130526192220051f, -0.923879532511287f, 0.38268343236509f,
+ -0.793353340291235f, 0.608761429008721f, -0.608761429008721f, 0.793353340291235f, -0.38268343236509f, 0.923879532511287f, -0.130526192220052f, 0.99144486137381f,
+ 0.38268343236509f, 0.923879532511287f, 0.923879532511287f, 0.38268343236509f, 0.923879532511287f, -0.38268343236509f, 0.38268343236509f, -0.923879532511287f,
+ -0.38268343236509f, -0.923879532511287f, -0.923879532511287f, -0.38268343236509f, -0.923879532511287f, 0.38268343236509f, -0.38268343236509f, 0.923879532511287f,
+ };
+
+ private static readonly float[] RandVecs2D =
+ {
+ -0.2700222198f, -0.9628540911f, 0.3863092627f, -0.9223693152f, 0.04444859006f, -0.999011673f, -0.5992523158f, -0.8005602176f, -0.7819280288f, 0.6233687174f, 0.9464672271f, 0.3227999196f, -0.6514146797f, -0.7587218957f, 0.9378472289f, 0.347048376f,
+ -0.8497875957f, -0.5271252623f, -0.879042592f, 0.4767432447f, -0.892300288f, -0.4514423508f, -0.379844434f, -0.9250503802f, -0.9951650832f, 0.0982163789f, 0.7724397808f, -0.6350880136f, 0.7573283322f, -0.6530343002f, -0.9928004525f, -0.119780055f,
+ -0.0532665713f, 0.9985803285f, 0.9754253726f, -0.2203300762f, -0.7665018163f, 0.6422421394f, 0.991636706f, 0.1290606184f, -0.994696838f, 0.1028503788f, -0.5379205513f, -0.84299554f, 0.5022815471f, -0.8647041387f, 0.4559821461f, -0.8899889226f,
+ -0.8659131224f, -0.5001944266f, 0.0879458407f, -0.9961252577f, -0.5051684983f, 0.8630207346f, 0.7753185226f, -0.6315704146f, -0.6921944612f, 0.7217110418f, -0.5191659449f, -0.8546734591f, 0.8978622882f, -0.4402764035f, -0.1706774107f, 0.9853269617f,
+ -0.9353430106f, -0.3537420705f, -0.9992404798f, 0.03896746794f, -0.2882064021f, -0.9575683108f, -0.9663811329f, 0.2571137995f, -0.8759714238f, -0.4823630009f, -0.8303123018f, -0.5572983775f, 0.05110133755f, -0.9986934731f, -0.8558373281f, -0.5172450752f,
+ 0.09887025282f, 0.9951003332f, 0.9189016087f, 0.3944867976f, -0.2439375892f, -0.9697909324f, -0.8121409387f, -0.5834613061f, -0.9910431363f, 0.1335421355f, 0.8492423985f, -0.5280031709f, -0.9717838994f, -0.2358729591f, 0.9949457207f, 0.1004142068f,
+ 0.6241065508f, -0.7813392434f, 0.662910307f, 0.7486988212f, -0.7197418176f, 0.6942418282f, -0.8143370775f, -0.5803922158f, 0.104521054f, -0.9945226741f, -0.1065926113f, -0.9943027784f, 0.445799684f, -0.8951327509f, 0.105547406f, 0.9944142724f,
+ -0.992790267f, 0.1198644477f, -0.8334366408f, 0.552615025f, 0.9115561563f, -0.4111755999f, 0.8285544909f, -0.5599084351f, 0.7217097654f, -0.6921957921f, 0.4940492677f, -0.8694339084f, -0.3652321272f, -0.9309164803f, -0.9696606758f, 0.2444548501f,
+ 0.08925509731f, -0.996008799f, 0.5354071276f, -0.8445941083f, -0.1053576186f, 0.9944343981f, -0.9890284586f, 0.1477251101f, 0.004856104961f, 0.9999882091f, 0.9885598478f, 0.1508291331f, 0.9286129562f, -0.3710498316f, -0.5832393863f, -0.8123003252f,
+ 0.3015207509f, 0.9534596146f, -0.9575110528f, 0.2883965738f, 0.9715802154f, -0.2367105511f, 0.229981792f, 0.9731949318f, 0.955763816f, -0.2941352207f, 0.740956116f, 0.6715534485f, -0.9971513787f, -0.07542630764f, 0.6905710663f, -0.7232645452f,
+ -0.290713703f, -0.9568100872f, 0.5912777791f, -0.8064679708f, -0.9454592212f, -0.325740481f, 0.6664455681f, 0.74555369f, 0.6236134912f, 0.7817328275f, 0.9126993851f, -0.4086316587f, -0.8191762011f, 0.5735419353f, -0.8812745759f, -0.4726046147f,
+ 0.9953313627f, 0.09651672651f, 0.9855650846f, -0.1692969699f, -0.8495980887f, 0.5274306472f, 0.6174853946f, -0.7865823463f, 0.8508156371f, 0.52546432f, 0.9985032451f, -0.05469249926f, 0.1971371563f, -0.9803759185f, 0.6607855748f, -0.7505747292f,
+ -0.03097494063f, 0.9995201614f, -0.6731660801f, 0.739491331f, -0.7195018362f, -0.6944905383f, 0.9727511689f, 0.2318515979f, 0.9997059088f, -0.0242506907f, 0.4421787429f, -0.8969269532f, 0.9981350961f, -0.061043673f, -0.9173660799f, -0.3980445648f,
+ -0.8150056635f, -0.5794529907f, -0.8789331304f, 0.4769450202f, 0.0158605829f, 0.999874213f, -0.8095464474f, 0.5870558317f, -0.9165898907f, -0.3998286786f, -0.8023542565f, 0.5968480938f, -0.5176737917f, 0.8555780767f, -0.8154407307f, -0.5788405779f,
+ 0.4022010347f, -0.9155513791f, -0.9052556868f, -0.4248672045f, 0.7317445619f, 0.6815789728f, -0.5647632201f, -0.8252529947f, -0.8403276335f, -0.5420788397f, -0.9314281527f, 0.363925262f, 0.5238198472f, 0.8518290719f, 0.7432803869f, -0.6689800195f,
+ -0.985371561f, -0.1704197369f, 0.4601468731f, 0.88784281f, 0.825855404f, 0.5638819483f, 0.6182366099f, 0.7859920446f, 0.8331502863f, -0.553046653f, 0.1500307506f, 0.9886813308f, -0.662330369f, -0.7492119075f, -0.668598664f, 0.743623444f,
+ 0.7025606278f, 0.7116238924f, -0.5419389763f, -0.8404178401f, -0.3388616456f, 0.9408362159f, 0.8331530315f, 0.5530425174f, -0.2989720662f, -0.9542618632f, 0.2638522993f, 0.9645630949f, 0.124108739f, -0.9922686234f, -0.7282649308f, -0.6852956957f,
+ 0.6962500149f, 0.7177993569f, -0.9183535368f, 0.3957610156f, -0.6326102274f, -0.7744703352f, -0.9331891859f, -0.359385508f, -0.1153779357f, -0.9933216659f, 0.9514974788f, -0.3076565421f, -0.08987977445f, -0.9959526224f, 0.6678496916f, 0.7442961705f,
+ 0.7952400393f, -0.6062947138f, -0.6462007402f, -0.7631674805f, -0.2733598753f, 0.9619118351f, 0.9669590226f, -0.254931851f, -0.9792894595f, 0.2024651934f, -0.5369502995f, -0.8436138784f, -0.270036471f, -0.9628500944f, -0.6400277131f, 0.7683518247f,
+ -0.7854537493f, -0.6189203566f, 0.06005905383f, -0.9981948257f, -0.02455770378f, 0.9996984141f, -0.65983623f, 0.751409442f, -0.6253894466f, -0.7803127835f, -0.6210408851f, -0.7837781695f, 0.8348888491f, 0.5504185768f, -0.1592275245f, 0.9872419133f,
+ 0.8367622488f, 0.5475663786f, -0.8675753916f, -0.4973056806f, -0.2022662628f, -0.9793305667f, 0.9399189937f, 0.3413975472f, 0.9877404807f, -0.1561049093f, -0.9034455656f, 0.4287028224f, 0.1269804218f, -0.9919052235f, -0.3819600854f, 0.924178821f,
+ 0.9754625894f, 0.2201652486f, -0.3204015856f, -0.9472818081f, -0.9874760884f, 0.1577687387f, 0.02535348474f, -0.9996785487f, 0.4835130794f, -0.8753371362f, -0.2850799925f, -0.9585037287f, -0.06805516006f, -0.99768156f, -0.7885244045f, -0.6150034663f,
+ 0.3185392127f, -0.9479096845f, 0.8880043089f, 0.4598351306f, 0.6476921488f, -0.7619021462f, 0.9820241299f, 0.1887554194f, 0.9357275128f, -0.3527237187f, -0.8894895414f, 0.4569555293f, 0.7922791302f, 0.6101588153f, 0.7483818261f, 0.6632681526f,
+ -0.7288929755f, -0.6846276581f, 0.8729032783f, -0.4878932944f, 0.8288345784f, 0.5594937369f, 0.08074567077f, 0.9967347374f, 0.9799148216f, -0.1994165048f, -0.580730673f, -0.8140957471f, -0.4700049791f, -0.8826637636f, 0.2409492979f, 0.9705377045f,
+ 0.9437816757f, -0.3305694308f, -0.8927998638f, -0.4504535528f, -0.8069622304f, 0.5906030467f, 0.06258973166f, 0.9980393407f, -0.9312597469f, 0.3643559849f, 0.5777449785f, 0.8162173362f, -0.3360095855f, -0.941858566f, 0.697932075f, -0.7161639607f,
+ -0.002008157227f, -0.9999979837f, -0.1827294312f, -0.9831632392f, -0.6523911722f, 0.7578824173f, -0.4302626911f, -0.9027037258f, -0.9985126289f, -0.05452091251f, -0.01028102172f, -0.9999471489f, -0.4946071129f, 0.8691166802f, -0.2999350194f, 0.9539596344f,
+ 0.8165471961f, 0.5772786819f, 0.2697460475f, 0.962931498f, -0.7306287391f, -0.6827749597f, -0.7590952064f, -0.6509796216f, -0.907053853f, 0.4210146171f, -0.5104861064f, -0.8598860013f, 0.8613350597f, 0.5080373165f, 0.5007881595f, -0.8655698812f,
+ -0.654158152f, 0.7563577938f, -0.8382755311f, -0.545246856f, 0.6940070834f, 0.7199681717f, 0.06950936031f, 0.9975812994f, 0.1702942185f, -0.9853932612f, 0.2695973274f, 0.9629731466f, 0.5519612192f, -0.8338697815f, 0.225657487f, -0.9742067022f,
+ 0.4215262855f, -0.9068161835f, 0.4881873305f, -0.8727388672f, -0.3683854996f, -0.9296731273f, -0.9825390578f, 0.1860564427f, 0.81256471f, 0.5828709909f, 0.3196460933f, -0.9475370046f, 0.9570913859f, 0.2897862643f, -0.6876655497f, -0.7260276109f,
+ -0.9988770922f, -0.047376731f, -0.1250179027f, 0.992154486f, -0.8280133617f, 0.560708367f, 0.9324863769f, -0.3612051451f, 0.6394653183f, 0.7688199442f, -0.01623847064f, -0.9998681473f, -0.9955014666f, -0.09474613458f, -0.81453315f, 0.580117012f,
+ 0.4037327978f, -0.9148769469f, 0.9944263371f, 0.1054336766f, -0.1624711654f, 0.9867132919f, -0.9949487814f, -0.100383875f, -0.6995302564f, 0.7146029809f, 0.5263414922f, -0.85027327f, -0.5395221479f, 0.841971408f, 0.6579370318f, 0.7530729462f,
+ 0.01426758847f, -0.9998982128f, -0.6734383991f, 0.7392433447f, 0.639412098f, -0.7688642071f, 0.9211571421f, 0.3891908523f, -0.146637214f, -0.9891903394f, -0.782318098f, 0.6228791163f, -0.5039610839f, -0.8637263605f, -0.7743120191f, -0.6328039957f,
+ };
+
+ private static readonly float[] Gradients3D =
+ {
+ 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0,
+ 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0,
+ 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0,
+ 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0,
+ 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0,
+ 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0,
+ 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0,
+ 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0,
+ 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0,
+ 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0,
+ 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0,
+ 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0,
+ 0, 1, 1, 0, 0,-1, 1, 0, 0, 1,-1, 0, 0,-1,-1, 0,
+ 1, 0, 1, 0, -1, 0, 1, 0, 1, 0,-1, 0, -1, 0,-1, 0,
+ 1, 1, 0, 0, -1, 1, 0, 0, 1,-1, 0, 0, -1,-1, 0, 0,
+ 1, 1, 0, 0, 0,-1, 1, 0, -1, 1, 0, 0, 0,-1,-1, 0
+ };
+
+ private static readonly float[] RandVecs3D =
+ {
+ -0.7292736885f, -0.6618439697f, 0.1735581948f, 0, 0.790292081f, -0.5480887466f, -0.2739291014f, 0, 0.7217578935f, 0.6226212466f, -0.3023380997f, 0, 0.565683137f, -0.8208298145f, -0.0790000257f, 0, 0.760049034f, -0.5555979497f, -0.3370999617f, 0, 0.3713945616f, 0.5011264475f, 0.7816254623f, 0, -0.1277062463f, -0.4254438999f, -0.8959289049f, 0, -0.2881560924f, -0.5815838982f, 0.7607405838f, 0,
+ 0.5849561111f, -0.662820239f, -0.4674352136f, 0, 0.3307171178f, 0.0391653737f, 0.94291689f, 0, 0.8712121778f, -0.4113374369f, -0.2679381538f, 0, 0.580981015f, 0.7021915846f, 0.4115677815f, 0, 0.503756873f, 0.6330056931f, -0.5878203852f, 0, 0.4493712205f, 0.601390195f, 0.6606022552f, 0, -0.6878403724f, 0.09018890807f, -0.7202371714f, 0, -0.5958956522f, -0.6469350577f, 0.475797649f, 0,
+ -0.5127052122f, 0.1946921978f, -0.8361987284f, 0, -0.9911507142f, -0.05410276466f, -0.1212153153f, 0, -0.2149721042f, 0.9720882117f, -0.09397607749f, 0, -0.7518650936f, -0.5428057603f, 0.3742469607f, 0, 0.5237068895f, 0.8516377189f, -0.02107817834f, 0, 0.6333504779f, 0.1926167129f, -0.7495104896f, 0, -0.06788241606f, 0.3998305789f, 0.9140719259f, 0, -0.5538628599f, -0.4729896695f, -0.6852128902f, 0,
+ -0.7261455366f, -0.5911990757f, 0.3509933228f, 0, -0.9229274737f, -0.1782808786f, 0.3412049336f, 0, -0.6968815002f, 0.6511274338f, 0.3006480328f, 0, 0.9608044783f, -0.2098363234f, -0.1811724921f, 0, 0.06817146062f, -0.9743405129f, 0.2145069156f, 0, -0.3577285196f, -0.6697087264f, -0.6507845481f, 0, -0.1868621131f, 0.7648617052f, -0.6164974636f, 0, -0.6541697588f, 0.3967914832f, 0.6439087246f, 0,
+ 0.6993340405f, -0.6164538506f, 0.3618239211f, 0, -0.1546665739f, 0.6291283928f, 0.7617583057f, 0, -0.6841612949f, -0.2580482182f, -0.6821542638f, 0, 0.5383980957f, 0.4258654885f, 0.7271630328f, 0, -0.5026987823f, -0.7939832935f, -0.3418836993f, 0, 0.3202971715f, 0.2834415347f, 0.9039195862f, 0, 0.8683227101f, -0.0003762656404f, -0.4959995258f, 0, 0.791120031f, -0.08511045745f, 0.6057105799f, 0,
+ -0.04011016052f, -0.4397248749f, 0.8972364289f, 0, 0.9145119872f, 0.3579346169f, -0.1885487608f, 0, -0.9612039066f, -0.2756484276f, 0.01024666929f, 0, 0.6510361721f, -0.2877799159f, -0.7023778346f, 0, -0.2041786351f, 0.7365237271f, 0.644859585f, 0, -0.7718263711f, 0.3790626912f, 0.5104855816f, 0, -0.3060082741f, -0.7692987727f, 0.5608371729f, 0, 0.454007341f, -0.5024843065f, 0.7357899537f, 0,
+ 0.4816795475f, 0.6021208291f, -0.6367380315f, 0, 0.6961980369f, -0.3222197429f, 0.641469197f, 0, -0.6532160499f, -0.6781148932f, 0.3368515753f, 0, 0.5089301236f, -0.6154662304f, -0.6018234363f, 0, -0.1635919754f, -0.9133604627f, -0.372840892f, 0, 0.52408019f, -0.8437664109f, 0.1157505864f, 0, 0.5902587356f, 0.4983817807f, -0.6349883666f, 0, 0.5863227872f, 0.494764745f, 0.6414307729f, 0,
+ 0.6779335087f, 0.2341345225f, 0.6968408593f, 0, 0.7177054546f, -0.6858979348f, 0.120178631f, 0, -0.5328819713f, -0.5205125012f, 0.6671608058f, 0, -0.8654874251f, -0.0700727088f, -0.4960053754f, 0, -0.2861810166f, 0.7952089234f, 0.5345495242f, 0, -0.04849529634f, 0.9810836427f, -0.1874115585f, 0, -0.6358521667f, 0.6058348682f, 0.4781800233f, 0, 0.6254794696f, -0.2861619734f, 0.7258696564f, 0,
+ -0.2585259868f, 0.5061949264f, -0.8227581726f, 0, 0.02136306781f, 0.5064016808f, -0.8620330371f, 0, 0.200111773f, 0.8599263484f, 0.4695550591f, 0, 0.4743561372f, 0.6014985084f, -0.6427953014f, 0, 0.6622993731f, -0.5202474575f, -0.5391679918f, 0, 0.08084972818f, -0.6532720452f, 0.7527940996f, 0, -0.6893687501f, 0.0592860349f, 0.7219805347f, 0, -0.1121887082f, -0.9673185067f, 0.2273952515f, 0,
+ 0.7344116094f, 0.5979668656f, -0.3210532909f, 0, 0.5789393465f, -0.2488849713f, 0.7764570201f, 0, 0.6988182827f, 0.3557169806f, -0.6205791146f, 0, -0.8636845529f, -0.2748771249f, -0.4224826141f, 0, -0.4247027957f, -0.4640880967f, 0.777335046f, 0, 0.5257722489f, -0.8427017621f, 0.1158329937f, 0, 0.9343830603f, 0.316302472f, -0.1639543925f, 0, -0.1016836419f, -0.8057303073f, -0.5834887393f, 0,
+ -0.6529238969f, 0.50602126f, -0.5635892736f, 0, -0.2465286165f, -0.9668205684f, -0.06694497494f, 0, -0.9776897119f, -0.2099250524f, -0.007368825344f, 0, 0.7736893337f, 0.5734244712f, 0.2694238123f, 0, -0.6095087895f, 0.4995678998f, 0.6155736747f, 0, 0.5794535482f, 0.7434546771f, 0.3339292269f, 0, -0.8226211154f, 0.08142581855f, 0.5627293636f, 0, -0.510385483f, 0.4703667658f, 0.7199039967f, 0,
+ -0.5764971849f, -0.07231656274f, -0.8138926898f, 0, 0.7250628871f, 0.3949971505f, -0.5641463116f, 0, -0.1525424005f, 0.4860840828f, -0.8604958341f, 0, -0.5550976208f, -0.4957820792f, 0.667882296f, 0, -0.1883614327f, 0.9145869398f, 0.357841725f, 0, 0.7625556724f, -0.5414408243f, -0.3540489801f, 0, -0.5870231946f, -0.3226498013f, -0.7424963803f, 0, 0.3051124198f, 0.2262544068f, -0.9250488391f, 0,
+ 0.6379576059f, 0.577242424f, -0.5097070502f, 0, -0.5966775796f, 0.1454852398f, -0.7891830656f, 0, -0.658330573f, 0.6555487542f, -0.3699414651f, 0, 0.7434892426f, 0.2351084581f, 0.6260573129f, 0, 0.5562114096f, 0.8264360377f, -0.0873632843f, 0, -0.3028940016f, -0.8251527185f, 0.4768419182f, 0, 0.1129343818f, -0.985888439f, -0.1235710781f, 0, 0.5937652891f, -0.5896813806f, 0.5474656618f, 0,
+ 0.6757964092f, -0.5835758614f, -0.4502648413f, 0, 0.7242302609f, -0.1152719764f, 0.6798550586f, 0, -0.9511914166f, 0.0753623979f, -0.2992580792f, 0, 0.2539470961f, -0.1886339355f, 0.9486454084f, 0, 0.571433621f, -0.1679450851f, -0.8032795685f, 0, -0.06778234979f, 0.3978269256f, 0.9149531629f, 0, 0.6074972649f, 0.733060024f, -0.3058922593f, 0, -0.5435478392f, 0.1675822484f, 0.8224791405f, 0,
+ -0.5876678086f, -0.3380045064f, -0.7351186982f, 0, -0.7967562402f, 0.04097822706f, -0.6029098428f, 0, -0.1996350917f, 0.8706294745f, 0.4496111079f, 0, -0.02787660336f, -0.9106232682f, -0.4122962022f, 0, -0.7797625996f, -0.6257634692f, 0.01975775581f, 0, -0.5211232846f, 0.7401644346f, -0.4249554471f, 0, 0.8575424857f, 0.4053272873f, -0.3167501783f, 0, 0.1045223322f, 0.8390195772f, -0.5339674439f, 0,
+ 0.3501822831f, 0.9242524096f, -0.1520850155f, 0, 0.1987849858f, 0.07647613266f, 0.9770547224f, 0, 0.7845996363f, 0.6066256811f, -0.1280964233f, 0, 0.09006737436f, -0.9750989929f, -0.2026569073f, 0, -0.8274343547f, -0.542299559f, 0.1458203587f, 0, -0.3485797732f, -0.415802277f, 0.840000362f, 0, -0.2471778936f, -0.7304819962f, -0.6366310879f, 0, -0.3700154943f, 0.8577948156f, 0.3567584454f, 0,
+ 0.5913394901f, -0.548311967f, -0.5913303597f, 0, 0.1204873514f, -0.7626472379f, -0.6354935001f, 0, 0.616959265f, 0.03079647928f, 0.7863922953f, 0, 0.1258156836f, -0.6640829889f, -0.7369967419f, 0, -0.6477565124f, -0.1740147258f, -0.7417077429f, 0, 0.6217889313f, -0.7804430448f, -0.06547655076f, 0, 0.6589943422f, -0.6096987708f, 0.4404473475f, 0, -0.2689837504f, -0.6732403169f, -0.6887635427f, 0,
+ -0.3849775103f, 0.5676542638f, 0.7277093879f, 0, 0.5754444408f, 0.8110471154f, -0.1051963504f, 0, 0.9141593684f, 0.3832947817f, 0.131900567f, 0, -0.107925319f, 0.9245493968f, 0.3654593525f, 0, 0.377977089f, 0.3043148782f, 0.8743716458f, 0, -0.2142885215f, -0.8259286236f, 0.5214617324f, 0, 0.5802544474f, 0.4148098596f, -0.7008834116f, 0, -0.1982660881f, 0.8567161266f, -0.4761596756f, 0,
+ -0.03381553704f, 0.3773180787f, -0.9254661404f, 0, -0.6867922841f, -0.6656597827f, 0.2919133642f, 0, 0.7731742607f, -0.2875793547f, -0.5652430251f, 0, -0.09655941928f, 0.9193708367f, -0.3813575004f, 0, 0.2715702457f, -0.9577909544f, -0.09426605581f, 0, 0.2451015704f, -0.6917998565f, -0.6792188003f, 0, 0.977700782f, -0.1753855374f, 0.1155036542f, 0, -0.5224739938f, 0.8521606816f, 0.02903615945f, 0,
+ -0.7734880599f, -0.5261292347f, 0.3534179531f, 0, -0.7134492443f, -0.269547243f, 0.6467878011f, 0, 0.1644037271f, 0.5105846203f, -0.8439637196f, 0, 0.6494635788f, 0.05585611296f, 0.7583384168f, 0, -0.4711970882f, 0.5017280509f, -0.7254255765f, 0, -0.6335764307f, -0.2381686273f, -0.7361091029f, 0, -0.9021533097f, -0.270947803f, -0.3357181763f, 0, -0.3793711033f, 0.872258117f, 0.3086152025f, 0,
+ -0.6855598966f, -0.3250143309f, 0.6514394162f, 0, 0.2900942212f, -0.7799057743f, -0.5546100667f, 0, -0.2098319339f, 0.85037073f, 0.4825351604f, 0, -0.4592603758f, 0.6598504336f, -0.5947077538f, 0, 0.8715945488f, 0.09616365406f, -0.4807031248f, 0, -0.6776666319f, 0.7118504878f, -0.1844907016f, 0, 0.7044377633f, 0.312427597f, 0.637304036f, 0, -0.7052318886f, -0.2401093292f, -0.6670798253f, 0,
+ 0.081921007f, -0.7207336136f, -0.6883545647f, 0, -0.6993680906f, -0.5875763221f, -0.4069869034f, 0, -0.1281454481f, 0.6419895885f, 0.7559286424f, 0, -0.6337388239f, -0.6785471501f, -0.3714146849f, 0, 0.5565051903f, -0.2168887573f, -0.8020356851f, 0, -0.5791554484f, 0.7244372011f, -0.3738578718f, 0, 0.1175779076f, -0.7096451073f, 0.6946792478f, 0, -0.6134619607f, 0.1323631078f, 0.7785527795f, 0,
+ 0.6984635305f, -0.02980516237f, -0.715024719f, 0, 0.8318082963f, -0.3930171956f, 0.3919597455f, 0, 0.1469576422f, 0.05541651717f, -0.9875892167f, 0, 0.708868575f, -0.2690503865f, 0.6520101478f, 0, 0.2726053183f, 0.67369766f, -0.68688995f, 0, -0.6591295371f, 0.3035458599f, -0.6880466294f, 0, 0.4815131379f, -0.7528270071f, 0.4487723203f, 0, 0.9430009463f, 0.1675647412f, -0.2875261255f, 0,
+ 0.434802957f, 0.7695304522f, -0.4677277752f, 0, 0.3931996188f, 0.594473625f, 0.7014236729f, 0, 0.7254336655f, -0.603925654f, 0.3301814672f, 0, 0.7590235227f, -0.6506083235f, 0.02433313207f, 0, -0.8552768592f, -0.3430042733f, 0.3883935666f, 0, -0.6139746835f, 0.6981725247f, 0.3682257648f, 0, -0.7465905486f, -0.5752009504f, 0.3342849376f, 0, 0.5730065677f, 0.810555537f, -0.1210916791f, 0,
+ -0.9225877367f, -0.3475211012f, -0.167514036f, 0, -0.7105816789f, -0.4719692027f, -0.5218416899f, 0, -0.08564609717f, 0.3583001386f, 0.929669703f, 0, -0.8279697606f, -0.2043157126f, 0.5222271202f, 0, 0.427944023f, 0.278165994f, 0.8599346446f, 0, 0.5399079671f, -0.7857120652f, -0.3019204161f, 0, 0.5678404253f, -0.5495413974f, -0.6128307303f, 0, -0.9896071041f, 0.1365639107f, -0.04503418428f, 0,
+ -0.6154342638f, -0.6440875597f, 0.4543037336f, 0, 0.1074204368f, -0.7946340692f, 0.5975094525f, 0, -0.3595449969f, -0.8885529948f, 0.28495784f, 0, -0.2180405296f, 0.1529888965f, 0.9638738118f, 0, -0.7277432317f, -0.6164050508f, -0.3007234646f, 0, 0.7249729114f, -0.00669719484f, 0.6887448187f, 0, -0.5553659455f, -0.5336586252f, 0.6377908264f, 0, 0.5137558015f, 0.7976208196f, -0.3160000073f, 0,
+ -0.3794024848f, 0.9245608561f, -0.03522751494f, 0, 0.8229248658f, 0.2745365933f, -0.4974176556f, 0, -0.5404114394f, 0.6091141441f, 0.5804613989f, 0, 0.8036581901f, -0.2703029469f, 0.5301601931f, 0, 0.6044318879f, 0.6832968393f, 0.4095943388f, 0, 0.06389988817f, 0.9658208605f, -0.2512108074f, 0, 0.1087113286f, 0.7402471173f, -0.6634877936f, 0, -0.713427712f, -0.6926784018f, 0.1059128479f, 0,
+ 0.6458897819f, -0.5724548511f, -0.5050958653f, 0, -0.6553931414f, 0.7381471625f, 0.159995615f, 0, 0.3910961323f, 0.9188871375f, -0.05186755998f, 0, -0.4879022471f, -0.5904376907f, 0.6429111375f, 0, 0.6014790094f, 0.7707441366f, -0.2101820095f, 0, -0.5677173047f, 0.7511360995f, 0.3368851762f, 0, 0.7858573506f, 0.226674665f, 0.5753666838f, 0, -0.4520345543f, -0.604222686f, -0.6561857263f, 0,
+ 0.002272116345f, 0.4132844051f, -0.9105991643f, 0, -0.5815751419f, -0.5162925989f, 0.6286591339f, 0, -0.03703704785f, 0.8273785755f, 0.5604221175f, 0, -0.5119692504f, 0.7953543429f, -0.3244980058f, 0, -0.2682417366f, -0.9572290247f, -0.1084387619f, 0, -0.2322482736f, -0.9679131102f, -0.09594243324f, 0, 0.3554328906f, -0.8881505545f, 0.2913006227f, 0, 0.7346520519f, -0.4371373164f, 0.5188422971f, 0,
+ 0.9985120116f, 0.04659011161f, -0.02833944577f, 0, -0.3727687496f, -0.9082481361f, 0.1900757285f, 0, 0.91737377f, -0.3483642108f, 0.1925298489f, 0, 0.2714911074f, 0.4147529736f, -0.8684886582f, 0, 0.5131763485f, -0.7116334161f, 0.4798207128f, 0, -0.8737353606f, 0.18886992f, -0.4482350644f, 0, 0.8460043821f, -0.3725217914f, 0.3814499973f, 0, 0.8978727456f, -0.1780209141f, -0.4026575304f, 0,
+ 0.2178065647f, -0.9698322841f, -0.1094789531f, 0, -0.1518031304f, -0.7788918132f, -0.6085091231f, 0, -0.2600384876f, -0.4755398075f, -0.8403819825f, 0, 0.572313509f, -0.7474340931f, -0.3373418503f, 0, -0.7174141009f, 0.1699017182f, -0.6756111411f, 0, -0.684180784f, 0.02145707593f, -0.7289967412f, 0, -0.2007447902f, 0.06555605789f, -0.9774476623f, 0, -0.1148803697f, -0.8044887315f, 0.5827524187f, 0,
+ -0.7870349638f, 0.03447489231f, 0.6159443543f, 0, -0.2015596421f, 0.6859872284f, 0.6991389226f, 0, -0.08581082512f, -0.10920836f, -0.9903080513f, 0, 0.5532693395f, 0.7325250401f, -0.396610771f, 0, -0.1842489331f, -0.9777375055f, -0.1004076743f, 0, 0.0775473789f, -0.9111505856f, 0.4047110257f, 0, 0.1399838409f, 0.7601631212f, -0.6344734459f, 0, 0.4484419361f, -0.845289248f, 0.2904925424f, 0
+ };
+
+
+ [MethodImpl(INLINE)]
+ private static float FastMin(float a, float b) { return a < b ? a : b; }
+
+ [MethodImpl(INLINE)]
+ private static float FastMax(float a, float b) { return a > b ? a : b; }
+
+ [MethodImpl(INLINE)]
+ private static float FastAbs(float f) { return f < 0 ? -f : f; }
+
+ [MethodImpl(INLINE)]
+ private static float FastSqrt(float f) { return (float)Math.Sqrt(f); }
+
+ [MethodImpl(INLINE)]
+ private static int FastFloor(FNLfloat f) { return f >= 0 ? (int)f : (int)f - 1; }
+
+ [MethodImpl(INLINE)]
+ private static int FastRound(FNLfloat f) { return f >= 0 ? (int)(f + 0.5f) : (int)(f - 0.5f); }
+
+ [MethodImpl(INLINE)]
+ private static float Lerp(float a, float b, float t) { return a + t * (b - a); }
+
+ [MethodImpl(INLINE)]
+ private static float InterpHermite(float t) { return t * t * (3 - 2 * t); }
+
+ [MethodImpl(INLINE)]
+ private static float InterpQuintic(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
+
+ [MethodImpl(INLINE)]
+ private static float CubicLerp(float a, float b, float c, float d, float t)
+ {
+ float p = (d - c) - (a - b);
+ return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b;
+ }
+
+ [MethodImpl(INLINE)]
+ private static float PingPong(float t)
+ {
+ t -= (int)(t * 0.5f) * 2;
+ return t < 1 ? t : 2 - t;
+ }
+
+ private void CalculateFractalBounding()
+ {
+ float gain = FastAbs(mGain);
+ float amp = gain;
+ float ampFractal = 1.0f;
+ for (int i = 1; i < mOctaves; i++)
+ {
+ ampFractal += amp;
+ amp *= gain;
+ }
+ mFractalBounding = 1 / ampFractal;
+ }
+
+ // Hashing
+ private const int PrimeX = 501125321;
+ private const int PrimeY = 1136930381;
+ private const int PrimeZ = 1720413743;
+
+ [MethodImpl(INLINE)]
+ private static int Hash(int seed, int xPrimed, int yPrimed)
+ {
+ int hash = seed ^ xPrimed ^ yPrimed;
+
+ hash *= 0x27d4eb2d;
+ return hash;
+ }
+
+ [MethodImpl(INLINE)]
+ private static int Hash(int seed, int xPrimed, int yPrimed, int zPrimed)
+ {
+ int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed;
+
+ hash *= 0x27d4eb2d;
+ return hash;
+ }
+
+ [MethodImpl(INLINE)]
+ private static float ValCoord(int seed, int xPrimed, int yPrimed)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed);
+
+ hash *= hash;
+ hash ^= hash << 19;
+ return hash * (1 / 2147483648.0f);
+ }
+
+ [MethodImpl(INLINE)]
+ private static float ValCoord(int seed, int xPrimed, int yPrimed, int zPrimed)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed);
+
+ hash *= hash;
+ hash ^= hash << 19;
+ return hash * (1 / 2147483648.0f);
+ }
+
+ [MethodImpl(INLINE)]
+ private static float GradCoord(int seed, int xPrimed, int yPrimed, float xd, float yd)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed);
+ hash ^= hash >> 15;
+ hash &= 127 << 1;
+
+ float xg = Gradients2D[hash];
+ float yg = Gradients2D[hash | 1];
+
+ return xd * xg + yd * yg;
+ }
+
+ [MethodImpl(INLINE)]
+ private static float GradCoord(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed);
+ hash ^= hash >> 15;
+ hash &= 63 << 2;
+
+ float xg = Gradients3D[hash];
+ float yg = Gradients3D[hash | 1];
+ float zg = Gradients3D[hash | 2];
+
+ return xd * xg + yd * yg + zd * zg;
+ }
+
+ [MethodImpl(INLINE)]
+ private static void GradCoordOut(int seed, int xPrimed, int yPrimed, out float xo, out float yo)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed) & (255 << 1);
+
+ xo = RandVecs2D[hash];
+ yo = RandVecs2D[hash | 1];
+ }
+
+ [MethodImpl(INLINE)]
+ private static void GradCoordOut(int seed, int xPrimed, int yPrimed, int zPrimed, out float xo, out float yo, out float zo)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed) & (255 << 2);
+
+ xo = RandVecs3D[hash];
+ yo = RandVecs3D[hash | 1];
+ zo = RandVecs3D[hash | 2];
+ }
+
+ [MethodImpl(INLINE)]
+ private static void GradCoordDual(int seed, int xPrimed, int yPrimed, float xd, float yd, out float xo, out float yo)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed);
+ int index1 = hash & (127 << 1);
+ int index2 = (hash >> 7) & (255 << 1);
+
+ float xg = Gradients2D[index1];
+ float yg = Gradients2D[index1 | 1];
+ float value = xd * xg + yd * yg;
+
+ float xgo = RandVecs2D[index2];
+ float ygo = RandVecs2D[index2 | 1];
+
+ xo = value * xgo;
+ yo = value * ygo;
+ }
+
+ [MethodImpl(INLINE)]
+ private static void GradCoordDual(int seed, int xPrimed, int yPrimed, int zPrimed, float xd, float yd, float zd, out float xo, out float yo, out float zo)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed);
+ int index1 = hash & (63 << 2);
+ int index2 = (hash >> 6) & (255 << 2);
+
+ float xg = Gradients3D[index1];
+ float yg = Gradients3D[index1 | 1];
+ float zg = Gradients3D[index1 | 2];
+ float value = xd * xg + yd * yg + zd * zg;
+
+ float xgo = RandVecs3D[index2];
+ float ygo = RandVecs3D[index2 | 1];
+ float zgo = RandVecs3D[index2 | 2];
+
+ xo = value * xgo;
+ yo = value * ygo;
+ zo = value * zgo;
+ }
+
+
+ // Generic noise gen
+
+ private float GenNoiseSingle(int seed, FNLfloat x, FNLfloat y)
+ {
+ switch (mNoiseType)
+ {
+ case NoiseType.OpenSimplex2:
+ return SingleSimplex(seed, x, y);
+ case NoiseType.OpenSimplex2S:
+ return SingleOpenSimplex2S(seed, x, y);
+ case NoiseType.Cellular:
+ return SingleCellular(seed, x, y);
+ case NoiseType.Perlin:
+ return SinglePerlin(seed, x, y);
+ case NoiseType.ValueCubic:
+ return SingleValueCubic(seed, x, y);
+ case NoiseType.Value:
+ return SingleValue(seed, x, y);
+ default:
+ return 0;
+ }
+ }
+
+ private float GenNoiseSingle(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ switch (mNoiseType)
+ {
+ case NoiseType.OpenSimplex2:
+ return SingleOpenSimplex2(seed, x, y, z);
+ case NoiseType.OpenSimplex2S:
+ return SingleOpenSimplex2S(seed, x, y, z);
+ case NoiseType.Cellular:
+ return SingleCellular(seed, x, y, z);
+ case NoiseType.Perlin:
+ return SinglePerlin(seed, x, y, z);
+ case NoiseType.ValueCubic:
+ return SingleValueCubic(seed, x, y, z);
+ case NoiseType.Value:
+ return SingleValue(seed, x, y, z);
+ default:
+ return 0;
+ }
+ }
+
+
+ // Noise Coordinate Transforms (frequency, and possible skew or rotation)
+
+ [MethodImpl(INLINE)]
+ private void TransformNoiseCoordinate(ref FNLfloat x, ref FNLfloat y)
+ {
+ x *= mFrequency;
+ y *= mFrequency;
+
+ switch (mNoiseType)
+ {
+ case NoiseType.OpenSimplex2:
+ case NoiseType.OpenSimplex2S:
+ {
+ const FNLfloat SQRT3 = (FNLfloat)1.7320508075688772935274463415059;
+ const FNLfloat F2 = 0.5f * (SQRT3 - 1);
+ FNLfloat t = (x + y) * F2;
+ x += t;
+ y += t;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ [MethodImpl(INLINE)]
+ private void TransformNoiseCoordinate(ref FNLfloat x, ref FNLfloat y, ref FNLfloat z)
+ {
+ x *= mFrequency;
+ y *= mFrequency;
+ z *= mFrequency;
+
+ switch (mTransformType3D)
+ {
+ case TransformType3D.ImproveXYPlanes:
+ {
+ FNLfloat xy = x + y;
+ FNLfloat s2 = xy * -(FNLfloat)0.211324865405187;
+ z *= (FNLfloat)0.577350269189626;
+ x += s2 - z;
+ y = y + s2 - z;
+ z += xy * (FNLfloat)0.577350269189626;
+ }
+ break;
+ case TransformType3D.ImproveXZPlanes:
+ {
+ FNLfloat xz = x + z;
+ FNLfloat s2 = xz * -(FNLfloat)0.211324865405187;
+ y *= (FNLfloat)0.577350269189626;
+ x += s2 - y;
+ z += s2 - y;
+ y += xz * (FNLfloat)0.577350269189626;
+ }
+ break;
+ case TransformType3D.DefaultOpenSimplex2:
+ {
+ const FNLfloat R3 = (FNLfloat)(2.0 / 3.0);
+ FNLfloat r = (x + y + z) * R3; // Rotation, not skew
+ x = r - x;
+ y = r - y;
+ z = r - z;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void UpdateTransformType3D()
+ {
+ switch (mRotationType3D)
+ {
+ case RotationType3D.ImproveXYPlanes:
+ mTransformType3D = TransformType3D.ImproveXYPlanes;
+ break;
+ case RotationType3D.ImproveXZPlanes:
+ mTransformType3D = TransformType3D.ImproveXZPlanes;
+ break;
+ default:
+ switch (mNoiseType)
+ {
+ case NoiseType.OpenSimplex2:
+ case NoiseType.OpenSimplex2S:
+ mTransformType3D = TransformType3D.DefaultOpenSimplex2;
+ break;
+ default:
+ mTransformType3D = TransformType3D.None;
+ break;
+ }
+ break;
+ }
+ }
+
+
+ // Domain Warp Coordinate Transforms
+
+ [MethodImpl(INLINE)]
+ private void TransformDomainWarpCoordinate(ref FNLfloat x, ref FNLfloat y)
+ {
+ switch (mDomainWarpType)
+ {
+ case DomainWarpType.OpenSimplex2:
+ case DomainWarpType.OpenSimplex2Reduced:
+ {
+ const FNLfloat SQRT3 = (FNLfloat)1.7320508075688772935274463415059;
+ const FNLfloat F2 = 0.5f * (SQRT3 - 1);
+ FNLfloat t = (x + y) * F2;
+ x += t; y += t;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ [MethodImpl(INLINE)]
+ private void TransformDomainWarpCoordinate(ref FNLfloat x, ref FNLfloat y, ref FNLfloat z)
+ {
+ switch (mWarpTransformType3D)
+ {
+ case TransformType3D.ImproveXYPlanes:
+ {
+ FNLfloat xy = x + y;
+ FNLfloat s2 = xy * -(FNLfloat)0.211324865405187;
+ z *= (FNLfloat)0.577350269189626;
+ x += s2 - z;
+ y = y + s2 - z;
+ z += xy * (FNLfloat)0.577350269189626;
+ }
+ break;
+ case TransformType3D.ImproveXZPlanes:
+ {
+ FNLfloat xz = x + z;
+ FNLfloat s2 = xz * -(FNLfloat)0.211324865405187;
+ y *= (FNLfloat)0.577350269189626;
+ x += s2 - y; z += s2 - y;
+ y += xz * (FNLfloat)0.577350269189626;
+ }
+ break;
+ case TransformType3D.DefaultOpenSimplex2:
+ {
+ const FNLfloat R3 = (FNLfloat)(2.0 / 3.0);
+ FNLfloat r = (x + y + z) * R3; // Rotation, not skew
+ x = r - x;
+ y = r - y;
+ z = r - z;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void UpdateWarpTransformType3D()
+ {
+ switch (mRotationType3D)
+ {
+ case RotationType3D.ImproveXYPlanes:
+ mWarpTransformType3D = TransformType3D.ImproveXYPlanes;
+ break;
+ case RotationType3D.ImproveXZPlanes:
+ mWarpTransformType3D = TransformType3D.ImproveXZPlanes;
+ break;
+ default:
+ switch (mDomainWarpType)
+ {
+ case DomainWarpType.OpenSimplex2:
+ case DomainWarpType.OpenSimplex2Reduced:
+ mWarpTransformType3D = TransformType3D.DefaultOpenSimplex2;
+ break;
+ default:
+ mWarpTransformType3D = TransformType3D.None;
+ break;
+ }
+ break;
+ }
+ }
+
+
+ // Fractal FBm
+
+ private float GenFractalFBm(FNLfloat x, FNLfloat y)
+ {
+ int seed = mSeed;
+ float sum = 0;
+ float amp = mFractalBounding;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ float noise = GenNoiseSingle(seed++, x, y);
+ sum += noise * amp;
+ amp *= Lerp(1.0f, FastMin(noise + 1, 2) * 0.5f, mWeightedStrength);
+
+ x *= mLacunarity;
+ y *= mLacunarity;
+ amp *= mGain;
+ }
+
+ return sum;
+ }
+
+ private float GenFractalFBm(FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int seed = mSeed;
+ float sum = 0;
+ float amp = mFractalBounding;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ float noise = GenNoiseSingle(seed++, x, y, z);
+ sum += noise * amp;
+ amp *= Lerp(1.0f, (noise + 1) * 0.5f, mWeightedStrength);
+
+ x *= mLacunarity;
+ y *= mLacunarity;
+ z *= mLacunarity;
+ amp *= mGain;
+ }
+
+ return sum;
+ }
+
+
+ // Fractal Ridged
+
+ private float GenFractalRidged(FNLfloat x, FNLfloat y)
+ {
+ int seed = mSeed;
+ float sum = 0;
+ float amp = mFractalBounding;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ float noise = FastAbs(GenNoiseSingle(seed++, x, y));
+ sum += (noise * -2 + 1) * amp;
+ amp *= Lerp(1.0f, 1 - noise, mWeightedStrength);
+
+ x *= mLacunarity;
+ y *= mLacunarity;
+ amp *= mGain;
+ }
+
+ return sum;
+ }
+
+ private float GenFractalRidged(FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int seed = mSeed;
+ float sum = 0;
+ float amp = mFractalBounding;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ float noise = FastAbs(GenNoiseSingle(seed++, x, y, z));
+ sum += (noise * -2 + 1) * amp;
+ amp *= Lerp(1.0f, 1 - noise, mWeightedStrength);
+
+ x *= mLacunarity;
+ y *= mLacunarity;
+ z *= mLacunarity;
+ amp *= mGain;
+ }
+
+ return sum;
+ }
+
+
+ // Fractal PingPong
+
+ private float GenFractalPingPong(FNLfloat x, FNLfloat y)
+ {
+ int seed = mSeed;
+ float sum = 0;
+ float amp = mFractalBounding;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ float noise = PingPong((GenNoiseSingle(seed++, x, y) + 1) * mPingPongStrength);
+ sum += (noise - 0.5f) * 2 * amp;
+ amp *= Lerp(1.0f, noise, mWeightedStrength);
+
+ x *= mLacunarity;
+ y *= mLacunarity;
+ amp *= mGain;
+ }
+
+ return sum;
+ }
+
+ private float GenFractalPingPong(FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int seed = mSeed;
+ float sum = 0;
+ float amp = mFractalBounding;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ float noise = PingPong((GenNoiseSingle(seed++, x, y, z) + 1) * mPingPongStrength);
+ sum += (noise - 0.5f) * 2 * amp;
+ amp *= Lerp(1.0f, noise, mWeightedStrength);
+
+ x *= mLacunarity;
+ y *= mLacunarity;
+ z *= mLacunarity;
+ amp *= mGain;
+ }
+
+ return sum;
+ }
+
+
+ // Simplex/OpenSimplex2 Noise
+
+ private float SingleSimplex(int seed, FNLfloat x, FNLfloat y)
+ {
+ // 2D OpenSimplex2 case uses the same algorithm as ordinary Simplex.
+
+ const float SQRT3 = 1.7320508075688772935274463415059f;
+ const float G2 = (3 - SQRT3) / 6;
+
+ /*
+ * --- Skew moved to TransformNoiseCoordinate method ---
+ * const FNfloat F2 = 0.5f * (SQRT3 - 1);
+ * FNfloat s = (x + y) * F2;
+ * x += s; y += s;
+ */
+
+ int i = FastFloor(x);
+ int j = FastFloor(y);
+ float xi = (float)(x - i);
+ float yi = (float)(y - j);
+
+ float t = (xi + yi) * G2;
+ float x0 = (float)(xi - t);
+ float y0 = (float)(yi - t);
+
+ i *= PrimeX;
+ j *= PrimeY;
+
+ float n0, n1, n2;
+
+ float a = 0.5f - x0 * x0 - y0 * y0;
+ if (a <= 0) n0 = 0;
+ else
+ {
+ n0 = (a * a) * (a * a) * GradCoord(seed, i, j, x0, y0);
+ }
+
+ float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a);
+ if (c <= 0) n2 = 0;
+ else
+ {
+ float x2 = x0 + (2 * (float)G2 - 1);
+ float y2 = y0 + (2 * (float)G2 - 1);
+ n2 = (c * c) * (c * c) * GradCoord(seed, i + PrimeX, j + PrimeY, x2, y2);
+ }
+
+ if (y0 > x0)
+ {
+ float x1 = x0 + (float)G2;
+ float y1 = y0 + ((float)G2 - 1);
+ float b = 0.5f - x1 * x1 - y1 * y1;
+ if (b <= 0) n1 = 0;
+ else
+ {
+ n1 = (b * b) * (b * b) * GradCoord(seed, i, j + PrimeY, x1, y1);
+ }
+ }
+ else
+ {
+ float x1 = x0 + ((float)G2 - 1);
+ float y1 = y0 + (float)G2;
+ float b = 0.5f - x1 * x1 - y1 * y1;
+ if (b <= 0) n1 = 0;
+ else
+ {
+ n1 = (b * b) * (b * b) * GradCoord(seed, i + PrimeX, j, x1, y1);
+ }
+ }
+
+ return (n0 + n1 + n2) * 99.83685446303647f;
+ }
+
+ private float SingleOpenSimplex2(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ // 3D OpenSimplex2 case uses two offset rotated cube grids.
+
+ /*
+ * --- Rotation moved to TransformNoiseCoordinate method ---
+ * const FNfloat R3 = (FNfloat)(2.0 / 3.0);
+ * FNfloat r = (x + y + z) * R3; // Rotation, not skew
+ * x = r - x; y = r - y; z = r - z;
+ */
+
+ int i = FastRound(x);
+ int j = FastRound(y);
+ int k = FastRound(z);
+ float x0 = (float)(x - i);
+ float y0 = (float)(y - j);
+ float z0 = (float)(z - k);
+
+ int xNSign = (int)(-1.0f - x0) | 1;
+ int yNSign = (int)(-1.0f - y0) | 1;
+ int zNSign = (int)(-1.0f - z0) | 1;
+
+ float ax0 = xNSign * -x0;
+ float ay0 = yNSign * -y0;
+ float az0 = zNSign * -z0;
+
+ i *= PrimeX;
+ j *= PrimeY;
+ k *= PrimeZ;
+
+ float value = 0;
+ float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0);
+
+ for (int l = 0; ; l++)
+ {
+ if (a > 0)
+ {
+ value += (a * a) * (a * a) * GradCoord(seed, i, j, k, x0, y0, z0);
+ }
+
+ if (ax0 >= ay0 && ax0 >= az0)
+ {
+ float b = a + ax0 + ax0;
+ if (b > 1) {
+ b -= 1;
+ value += (b * b) * (b * b) * GradCoord(seed, i - xNSign * PrimeX, j, k, x0 + xNSign, y0, z0);
+ }
+ }
+ else if (ay0 > ax0 && ay0 >= az0)
+ {
+ float b = a + ay0 + ay0;
+ if (b > 1)
+ {
+ b -= 1;
+ value += (b * b) * (b * b) * GradCoord(seed, i, j - yNSign * PrimeY, k, x0, y0 + yNSign, z0);
+ }
+ }
+ else
+ {
+ float b = a + az0 + az0;
+ if (b > 1)
+ {
+ b -= 1;
+ value += (b * b) * (b * b) * GradCoord(seed, i, j, k - zNSign * PrimeZ, x0, y0, z0 + zNSign);
+ }
+ }
+
+ if (l == 1) break;
+
+ ax0 = 0.5f - ax0;
+ ay0 = 0.5f - ay0;
+ az0 = 0.5f - az0;
+
+ x0 = xNSign * ax0;
+ y0 = yNSign * ay0;
+ z0 = zNSign * az0;
+
+ a += (0.75f - ax0) - (ay0 + az0);
+
+ i += (xNSign >> 1) & PrimeX;
+ j += (yNSign >> 1) & PrimeY;
+ k += (zNSign >> 1) & PrimeZ;
+
+ xNSign = -xNSign;
+ yNSign = -yNSign;
+ zNSign = -zNSign;
+
+ seed = ~seed;
+ }
+
+ return value * 32.69428253173828125f;
+ }
+
+
+ // OpenSimplex2S Noise
+
+ private float SingleOpenSimplex2S(int seed, FNLfloat x, FNLfloat y)
+ {
+ // 2D OpenSimplex2S case is a modified 2D simplex noise.
+
+ const FNLfloat SQRT3 = (FNLfloat)1.7320508075688772935274463415059;
+ const FNLfloat G2 = (3 - SQRT3) / 6;
+
+ /*
+ * --- Skew moved to TransformNoiseCoordinate method ---
+ * const FNfloat F2 = 0.5f * (SQRT3 - 1);
+ * FNfloat s = (x + y) * F2;
+ * x += s; y += s;
+ */
+
+ int i = FastFloor(x);
+ int j = FastFloor(y);
+ float xi = (float)(x - i);
+ float yi = (float)(y - j);
+
+ i *= PrimeX;
+ j *= PrimeY;
+ int i1 = i + PrimeX;
+ int j1 = j + PrimeY;
+
+ float t = (xi + yi) * (float)G2;
+ float x0 = xi - t;
+ float y0 = yi - t;
+
+ float a0 = (2.0f / 3.0f) - x0 * x0 - y0 * y0;
+ float value = (a0 * a0) * (a0 * a0) * GradCoord(seed, i, j, x0, y0);
+
+ float a1 = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a0);
+ float x1 = x0 - (float)(1 - 2 * G2);
+ float y1 = y0 - (float)(1 - 2 * G2);
+ value += (a1 * a1) * (a1 * a1) * GradCoord(seed, i1, j1, x1, y1);
+
+ // Nested conditionals were faster than compact bit logic/arithmetic.
+ float xmyi = xi - yi;
+ if (t > G2)
+ {
+ if (xi + xmyi > 1)
+ {
+ float x2 = x0 + (float)(3 * G2 - 2);
+ float y2 = y0 + (float)(3 * G2 - 1);
+ float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2;
+ if (a2 > 0)
+ {
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + (PrimeX << 1), j + PrimeY, x2, y2);
+ }
+ }
+ else
+ {
+ float x2 = x0 + (float)G2;
+ float y2 = y0 + (float)(G2 - 1);
+ float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2;
+ if (a2 > 0)
+ {
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2);
+ }
+ }
+
+ if (yi - xmyi > 1)
+ {
+ float x3 = x0 + (float)(3 * G2 - 1);
+ float y3 = y0 + (float)(3 * G2 - 2);
+ float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3;
+ if (a3 > 0)
+ {
+ value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j + (PrimeY << 1), x3, y3);
+ }
+ }
+ else
+ {
+ float x3 = x0 + (float)(G2 - 1);
+ float y3 = y0 + (float)G2;
+ float a3 = (2.0f / 3.0f) - x3 * x3 - y3 * y3;
+ if (a3 > 0)
+ {
+ value += (a3 * a3) * (a3 * a3) * GradCoord(seed, i + PrimeX, j, x3, y3);
+ }
+ }
+ }
+ else
+ {
+ if (xi + xmyi < 0)
+ {
+ float x2 = x0 + (float)(1 - G2);
+ float y2 = y0 - (float)G2;
+ float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2;
+ if (a2 > 0)
+ {
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i - PrimeX, j, x2, y2);
+ }
+ }
+ else
+ {
+ float x2 = x0 + (float)(G2 - 1);
+ float y2 = y0 + (float)G2;
+ float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2;
+ if (a2 > 0)
+ {
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i + PrimeX, j, x2, y2);
+ }
+ }
+
+ if (yi < xmyi)
+ {
+ float x2 = x0 - (float)G2;
+ float y2 = y0 - (float)(G2 - 1);
+ float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2;
+ if (a2 > 0)
+ {
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j - PrimeY, x2, y2);
+ }
+ }
+ else
+ {
+ float x2 = x0 + (float)G2;
+ float y2 = y0 + (float)(G2 - 1);
+ float a2 = (2.0f / 3.0f) - x2 * x2 - y2 * y2;
+ if (a2 > 0)
+ {
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed, i, j + PrimeY, x2, y2);
+ }
+ }
+ }
+
+ return value * 18.24196194486065f;
+ }
+
+ private float SingleOpenSimplex2S(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ // 3D OpenSimplex2S case uses two offset rotated cube grids.
+
+ /*
+ * --- Rotation moved to TransformNoiseCoordinate method ---
+ * const FNfloat R3 = (FNfloat)(2.0 / 3.0);
+ * FNfloat r = (x + y + z) * R3; // Rotation, not skew
+ * x = r - x; y = r - y; z = r - z;
+ */
+
+ int i = FastFloor(x);
+ int j = FastFloor(y);
+ int k = FastFloor(z);
+ float xi = (float)(x - i);
+ float yi = (float)(y - j);
+ float zi = (float)(z - k);
+
+ i *= PrimeX;
+ j *= PrimeY;
+ k *= PrimeZ;
+ int seed2 = seed + 1293373;
+
+ int xNMask = (int)(-0.5f - xi);
+ int yNMask = (int)(-0.5f - yi);
+ int zNMask = (int)(-0.5f - zi);
+
+ float x0 = xi + xNMask;
+ float y0 = yi + yNMask;
+ float z0 = zi + zNMask;
+ float a0 = 0.75f - x0 * x0 - y0 * y0 - z0 * z0;
+ float value = (a0 * a0) * (a0 * a0) * GradCoord(seed,
+ i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x0, y0, z0);
+
+ float x1 = xi - 0.5f;
+ float y1 = yi - 0.5f;
+ float z1 = zi - 0.5f;
+ float a1 = 0.75f - x1 * x1 - y1 * y1 - z1 * z1;
+ value += (a1 * a1) * (a1 * a1) * GradCoord(seed2,
+ i + PrimeX, j + PrimeY, k + PrimeZ, x1, y1, z1);
+
+ float xAFlipMask0 = ((xNMask | 1) << 1) * x1;
+ float yAFlipMask0 = ((yNMask | 1) << 1) * y1;
+ float zAFlipMask0 = ((zNMask | 1) << 1) * z1;
+ float xAFlipMask1 = (-2 - (xNMask << 2)) * x1 - 1.0f;
+ float yAFlipMask1 = (-2 - (yNMask << 2)) * y1 - 1.0f;
+ float zAFlipMask1 = (-2 - (zNMask << 2)) * z1 - 1.0f;
+
+ bool skip5 = false;
+ float a2 = xAFlipMask0 + a0;
+ if (a2 > 0)
+ {
+ float x2 = x0 - (xNMask | 1);
+ float y2 = y0;
+ float z2 = z0;
+ value += (a2 * a2) * (a2 * a2) * GradCoord(seed,
+ i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (zNMask & PrimeZ), x2, y2, z2);
+ }
+ else
+ {
+ float a3 = yAFlipMask0 + zAFlipMask0 + a0;
+ if (a3 > 0)
+ {
+ float x3 = x0;
+ float y3 = y0 - (yNMask | 1);
+ float z3 = z0 - (zNMask | 1);
+ value += (a3 * a3) * (a3 * a3) * GradCoord(seed,
+ i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (~zNMask & PrimeZ), x3, y3, z3);
+ }
+
+ float a4 = xAFlipMask1 + a1;
+ if (a4 > 0)
+ {
+ float x4 = (xNMask | 1) + x1;
+ float y4 = y1;
+ float z4 = z1;
+ value += (a4 * a4) * (a4 * a4) * GradCoord(seed2,
+ i + (xNMask & (PrimeX * 2)), j + PrimeY, k + PrimeZ, x4, y4, z4);
+ skip5 = true;
+ }
+ }
+
+ bool skip9 = false;
+ float a6 = yAFlipMask0 + a0;
+ if (a6 > 0)
+ {
+ float x6 = x0;
+ float y6 = y0 - (yNMask | 1);
+ float z6 = z0;
+ value += (a6 * a6) * (a6 * a6) * GradCoord(seed,
+ i + (xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), x6, y6, z6);
+ }
+ else
+ {
+ float a7 = xAFlipMask0 + zAFlipMask0 + a0;
+ if (a7 > 0)
+ {
+ float x7 = x0 - (xNMask | 1);
+ float y7 = y0;
+ float z7 = z0 - (zNMask | 1);
+ value += (a7 * a7) * (a7 * a7) * GradCoord(seed,
+ i + (~xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), x7, y7, z7);
+ }
+
+ float a8 = yAFlipMask1 + a1;
+ if (a8 > 0)
+ {
+ float x8 = x1;
+ float y8 = (yNMask | 1) + y1;
+ float z8 = z1;
+ value += (a8 * a8) * (a8 * a8) * GradCoord(seed2,
+ i + PrimeX, j + (yNMask & (PrimeY << 1)), k + PrimeZ, x8, y8, z8);
+ skip9 = true;
+ }
+ }
+
+ bool skipD = false;
+ float aA = zAFlipMask0 + a0;
+ if (aA > 0)
+ {
+ float xA = x0;
+ float yA = y0;
+ float zA = z0 - (zNMask | 1);
+ value += (aA * aA) * (aA * aA) * GradCoord(seed,
+ i + (xNMask & PrimeX), j + (yNMask & PrimeY), k + (~zNMask & PrimeZ), xA, yA, zA);
+ }
+ else
+ {
+ float aB = xAFlipMask0 + yAFlipMask0 + a0;
+ if (aB > 0)
+ {
+ float xB = x0 - (xNMask | 1);
+ float yB = y0 - (yNMask | 1);
+ float zB = z0;
+ value += (aB * aB) * (aB * aB) * GradCoord(seed,
+ i + (~xNMask & PrimeX), j + (~yNMask & PrimeY), k + (zNMask & PrimeZ), xB, yB, zB);
+ }
+
+ float aC = zAFlipMask1 + a1;
+ if (aC > 0)
+ {
+ float xC = x1;
+ float yC = y1;
+ float zC = (zNMask | 1) + z1;
+ value += (aC * aC) * (aC * aC) * GradCoord(seed2,
+ i + PrimeX, j + PrimeY, k + (zNMask & (PrimeZ << 1)), xC, yC, zC);
+ skipD = true;
+ }
+ }
+
+ if (!skip5)
+ {
+ float a5 = yAFlipMask1 + zAFlipMask1 + a1;
+ if (a5 > 0)
+ {
+ float x5 = x1;
+ float y5 = (yNMask | 1) + y1;
+ float z5 = (zNMask | 1) + z1;
+ value += (a5 * a5) * (a5 * a5) * GradCoord(seed2,
+ i + PrimeX, j + (yNMask & (PrimeY << 1)), k + (zNMask & (PrimeZ << 1)), x5, y5, z5);
+ }
+ }
+
+ if (!skip9)
+ {
+ float a9 = xAFlipMask1 + zAFlipMask1 + a1;
+ if (a9 > 0)
+ {
+ float x9 = (xNMask | 1) + x1;
+ float y9 = y1;
+ float z9 = (zNMask | 1) + z1;
+ value += (a9 * a9) * (a9 * a9) * GradCoord(seed2,
+ i + (xNMask & (PrimeX * 2)), j + PrimeY, k + (zNMask & (PrimeZ << 1)), x9, y9, z9);
+ }
+ }
+
+ if (!skipD)
+ {
+ float aD = xAFlipMask1 + yAFlipMask1 + a1;
+ if (aD > 0)
+ {
+ float xD = (xNMask | 1) + x1;
+ float yD = (yNMask | 1) + y1;
+ float zD = z1;
+ value += (aD * aD) * (aD * aD) * GradCoord(seed2,
+ i + (xNMask & (PrimeX << 1)), j + (yNMask & (PrimeY << 1)), k + PrimeZ, xD, yD, zD);
+ }
+ }
+
+ return value * 9.046026385208288f;
+ }
+
+
+ // Cellular Noise
+
+ private float SingleCellular(int seed, FNLfloat x, FNLfloat y)
+ {
+ int xr = FastRound(x);
+ int yr = FastRound(y);
+
+ float distance0 = float.MaxValue;
+ float distance1 = float.MaxValue;
+ int closestHash = 0;
+
+ float cellularJitter = 0.43701595f * mCellularJitterModifier;
+
+ int xPrimed = (xr - 1) * PrimeX;
+ int yPrimedBase = (yr - 1) * PrimeY;
+
+ switch (mCellularDistanceFunction)
+ {
+ default:
+ case CellularDistanceFunction.Euclidean:
+ case CellularDistanceFunction.EuclideanSq:
+ for (int xi = xr - 1; xi <= xr + 1; xi++)
+ {
+ int yPrimed = yPrimedBase;
+
+ for (int yi = yr - 1; yi <= yr + 1; yi++)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed);
+ int idx = hash & (255 << 1);
+
+ float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter;
+ float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter;
+
+ float newDistance = vecX * vecX + vecY * vecY;
+
+ distance1 = FastMax(FastMin(distance1, newDistance), distance0);
+ if (newDistance < distance0)
+ {
+ distance0 = newDistance;
+ closestHash = hash;
+ }
+ yPrimed += PrimeY;
+ }
+ xPrimed += PrimeX;
+ }
+ break;
+ case CellularDistanceFunction.Manhattan:
+ for (int xi = xr - 1; xi <= xr + 1; xi++)
+ {
+ int yPrimed = yPrimedBase;
+
+ for (int yi = yr - 1; yi <= yr + 1; yi++)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed);
+ int idx = hash & (255 << 1);
+
+ float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter;
+ float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter;
+
+ float newDistance = FastAbs(vecX) + FastAbs(vecY);
+
+ distance1 = FastMax(FastMin(distance1, newDistance), distance0);
+ if (newDistance < distance0)
+ {
+ distance0 = newDistance;
+ closestHash = hash;
+ }
+ yPrimed += PrimeY;
+ }
+ xPrimed += PrimeX;
+ }
+ break;
+ case CellularDistanceFunction.Hybrid:
+ for (int xi = xr - 1; xi <= xr + 1; xi++)
+ {
+ int yPrimed = yPrimedBase;
+
+ for (int yi = yr - 1; yi <= yr + 1; yi++)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed);
+ int idx = hash & (255 << 1);
+
+ float vecX = (float)(xi - x) + RandVecs2D[idx] * cellularJitter;
+ float vecY = (float)(yi - y) + RandVecs2D[idx | 1] * cellularJitter;
+
+ float newDistance = (FastAbs(vecX) + FastAbs(vecY)) + (vecX * vecX + vecY * vecY);
+
+ distance1 = FastMax(FastMin(distance1, newDistance), distance0);
+ if (newDistance < distance0)
+ {
+ distance0 = newDistance;
+ closestHash = hash;
+ }
+ yPrimed += PrimeY;
+ }
+ xPrimed += PrimeX;
+ }
+ break;
+ }
+
+ if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType >= CellularReturnType.Distance)
+ {
+ distance0 = FastSqrt(distance0);
+
+ if (mCellularReturnType >= CellularReturnType.Distance2)
+ {
+ distance1 = FastSqrt(distance1);
+ }
+ }
+
+ switch (mCellularReturnType)
+ {
+ case CellularReturnType.CellValue:
+ return closestHash * (1 / 2147483648.0f);
+ case CellularReturnType.Distance:
+ return distance0 - 1;
+ case CellularReturnType.Distance2:
+ return distance1 - 1;
+ case CellularReturnType.Distance2Add:
+ return (distance1 + distance0) * 0.5f - 1;
+ case CellularReturnType.Distance2Sub:
+ return distance1 - distance0 - 1;
+ case CellularReturnType.Distance2Mul:
+ return distance1 * distance0 * 0.5f - 1;
+ case CellularReturnType.Distance2Div:
+ return distance0 / distance1 - 1;
+ default:
+ return 0;
+ }
+ }
+
+ private float SingleCellular(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int xr = FastRound(x);
+ int yr = FastRound(y);
+ int zr = FastRound(z);
+
+ float distance0 = float.MaxValue;
+ float distance1 = float.MaxValue;
+ int closestHash = 0;
+
+ float cellularJitter = 0.39614353f * mCellularJitterModifier;
+
+ int xPrimed = (xr - 1) * PrimeX;
+ int yPrimedBase = (yr - 1) * PrimeY;
+ int zPrimedBase = (zr - 1) * PrimeZ;
+
+ switch (mCellularDistanceFunction)
+ {
+ case CellularDistanceFunction.Euclidean:
+ case CellularDistanceFunction.EuclideanSq:
+ for (int xi = xr - 1; xi <= xr + 1; xi++)
+ {
+ int yPrimed = yPrimedBase;
+
+ for (int yi = yr - 1; yi <= yr + 1; yi++)
+ {
+ int zPrimed = zPrimedBase;
+
+ for (int zi = zr - 1; zi <= zr + 1; zi++)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed);
+ int idx = hash & (255 << 2);
+
+ float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter;
+ float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter;
+ float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter;
+
+ float newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ;
+
+ distance1 = FastMax(FastMin(distance1, newDistance), distance0);
+ if (newDistance < distance0)
+ {
+ distance0 = newDistance;
+ closestHash = hash;
+ }
+ zPrimed += PrimeZ;
+ }
+ yPrimed += PrimeY;
+ }
+ xPrimed += PrimeX;
+ }
+ break;
+ case CellularDistanceFunction.Manhattan:
+ for (int xi = xr - 1; xi <= xr + 1; xi++)
+ {
+ int yPrimed = yPrimedBase;
+
+ for (int yi = yr - 1; yi <= yr + 1; yi++)
+ {
+ int zPrimed = zPrimedBase;
+
+ for (int zi = zr - 1; zi <= zr + 1; zi++)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed);
+ int idx = hash & (255 << 2);
+
+ float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter;
+ float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter;
+ float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter;
+
+ float newDistance = FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ);
+
+ distance1 = FastMax(FastMin(distance1, newDistance), distance0);
+ if (newDistance < distance0)
+ {
+ distance0 = newDistance;
+ closestHash = hash;
+ }
+ zPrimed += PrimeZ;
+ }
+ yPrimed += PrimeY;
+ }
+ xPrimed += PrimeX;
+ }
+ break;
+ case CellularDistanceFunction.Hybrid:
+ for (int xi = xr - 1; xi <= xr + 1; xi++)
+ {
+ int yPrimed = yPrimedBase;
+
+ for (int yi = yr - 1; yi <= yr + 1; yi++)
+ {
+ int zPrimed = zPrimedBase;
+
+ for (int zi = zr - 1; zi <= zr + 1; zi++)
+ {
+ int hash = Hash(seed, xPrimed, yPrimed, zPrimed);
+ int idx = hash & (255 << 2);
+
+ float vecX = (float)(xi - x) + RandVecs3D[idx] * cellularJitter;
+ float vecY = (float)(yi - y) + RandVecs3D[idx | 1] * cellularJitter;
+ float vecZ = (float)(zi - z) + RandVecs3D[idx | 2] * cellularJitter;
+
+ float newDistance = (FastAbs(vecX) + FastAbs(vecY) + FastAbs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ);
+
+ distance1 = FastMax(FastMin(distance1, newDistance), distance0);
+ if (newDistance < distance0)
+ {
+ distance0 = newDistance;
+ closestHash = hash;
+ }
+ zPrimed += PrimeZ;
+ }
+ yPrimed += PrimeY;
+ }
+ xPrimed += PrimeX;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (mCellularDistanceFunction == CellularDistanceFunction.Euclidean && mCellularReturnType >= CellularReturnType.Distance)
+ {
+ distance0 = FastSqrt(distance0);
+
+ if (mCellularReturnType >= CellularReturnType.Distance2)
+ {
+ distance1 = FastSqrt(distance1);
+ }
+ }
+
+ switch (mCellularReturnType)
+ {
+ case CellularReturnType.CellValue:
+ return closestHash * (1 / 2147483648.0f);
+ case CellularReturnType.Distance:
+ return distance0 - 1;
+ case CellularReturnType.Distance2:
+ return distance1 - 1;
+ case CellularReturnType.Distance2Add:
+ return (distance1 + distance0) * 0.5f - 1;
+ case CellularReturnType.Distance2Sub:
+ return distance1 - distance0 - 1;
+ case CellularReturnType.Distance2Mul:
+ return distance1 * distance0 * 0.5f - 1;
+ case CellularReturnType.Distance2Div:
+ return distance0 / distance1 - 1;
+ default:
+ return 0;
+ }
+ }
+
+
+ // Perlin Noise
+
+ private float SinglePerlin(int seed, FNLfloat x, FNLfloat y)
+ {
+ int x0 = FastFloor(x);
+ int y0 = FastFloor(y);
+
+ float xd0 = (float)(x - x0);
+ float yd0 = (float)(y - y0);
+ float xd1 = xd0 - 1;
+ float yd1 = yd0 - 1;
+
+ float xs = InterpQuintic(xd0);
+ float ys = InterpQuintic(yd0);
+
+ x0 *= PrimeX;
+ y0 *= PrimeY;
+ int x1 = x0 + PrimeX;
+ int y1 = y0 + PrimeY;
+
+ float xf0 = Lerp(GradCoord(seed, x0, y0, xd0, yd0), GradCoord(seed, x1, y0, xd1, yd0), xs);
+ float xf1 = Lerp(GradCoord(seed, x0, y1, xd0, yd1), GradCoord(seed, x1, y1, xd1, yd1), xs);
+
+ return Lerp(xf0, xf1, ys) * 1.4247691104677813f;
+ }
+
+ private float SinglePerlin(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int x0 = FastFloor(x);
+ int y0 = FastFloor(y);
+ int z0 = FastFloor(z);
+
+ float xd0 = (float)(x - x0);
+ float yd0 = (float)(y - y0);
+ float zd0 = (float)(z - z0);
+ float xd1 = xd0 - 1;
+ float yd1 = yd0 - 1;
+ float zd1 = zd0 - 1;
+
+ float xs = InterpQuintic(xd0);
+ float ys = InterpQuintic(yd0);
+ float zs = InterpQuintic(zd0);
+
+ x0 *= PrimeX;
+ y0 *= PrimeY;
+ z0 *= PrimeZ;
+ int x1 = x0 + PrimeX;
+ int y1 = y0 + PrimeY;
+ int z1 = z0 + PrimeZ;
+
+ float xf00 = Lerp(GradCoord(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord(seed, x1, y0, z0, xd1, yd0, zd0), xs);
+ float xf10 = Lerp(GradCoord(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord(seed, x1, y1, z0, xd1, yd1, zd0), xs);
+ float xf01 = Lerp(GradCoord(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord(seed, x1, y0, z1, xd1, yd0, zd1), xs);
+ float xf11 = Lerp(GradCoord(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord(seed, x1, y1, z1, xd1, yd1, zd1), xs);
+
+ float yf0 = Lerp(xf00, xf10, ys);
+ float yf1 = Lerp(xf01, xf11, ys);
+
+ return Lerp(yf0, yf1, zs) * 0.964921414852142333984375f;
+ }
+
+
+ // Value Cubic Noise
+
+ private float SingleValueCubic(int seed, FNLfloat x, FNLfloat y)
+ {
+ int x1 = FastFloor(x);
+ int y1 = FastFloor(y);
+
+ float xs = (float)(x - x1);
+ float ys = (float)(y - y1);
+
+ x1 *= PrimeX;
+ y1 *= PrimeY;
+ int x0 = x1 - PrimeX;
+ int y0 = y1 - PrimeY;
+ int x2 = x1 + PrimeX;
+ int y2 = y1 + PrimeY;
+ int x3 = x1 + unchecked(PrimeX * 2);
+ int y3 = y1 + unchecked(PrimeY * 2);
+
+ return CubicLerp(
+ CubicLerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), ValCoord(seed, x2, y0), ValCoord(seed, x3, y0),
+ xs),
+ CubicLerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), ValCoord(seed, x2, y1), ValCoord(seed, x3, y1),
+ xs),
+ CubicLerp(ValCoord(seed, x0, y2), ValCoord(seed, x1, y2), ValCoord(seed, x2, y2), ValCoord(seed, x3, y2),
+ xs),
+ CubicLerp(ValCoord(seed, x0, y3), ValCoord(seed, x1, y3), ValCoord(seed, x2, y3), ValCoord(seed, x3, y3),
+ xs),
+ ys) * (1 / (1.5f * 1.5f));
+ }
+
+ private float SingleValueCubic(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int x1 = FastFloor(x);
+ int y1 = FastFloor(y);
+ int z1 = FastFloor(z);
+
+ float xs = (float)(x - x1);
+ float ys = (float)(y - y1);
+ float zs = (float)(z - z1);
+
+ x1 *= PrimeX;
+ y1 *= PrimeY;
+ z1 *= PrimeZ;
+
+ int x0 = x1 - PrimeX;
+ int y0 = y1 - PrimeY;
+ int z0 = z1 - PrimeZ;
+ int x2 = x1 + PrimeX;
+ int y2 = y1 + PrimeY;
+ int z2 = z1 + PrimeZ;
+ int x3 = x1 + unchecked(PrimeX * 2);
+ int y3 = y1 + unchecked(PrimeY * 2);
+ int z3 = z1 + unchecked(PrimeZ * 2);
+
+
+ return CubicLerp(
+ CubicLerp(
+ CubicLerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), ValCoord(seed, x2, y0, z0), ValCoord(seed, x3, y0, z0), xs),
+ CubicLerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), ValCoord(seed, x2, y1, z0), ValCoord(seed, x3, y1, z0), xs),
+ CubicLerp(ValCoord(seed, x0, y2, z0), ValCoord(seed, x1, y2, z0), ValCoord(seed, x2, y2, z0), ValCoord(seed, x3, y2, z0), xs),
+ CubicLerp(ValCoord(seed, x0, y3, z0), ValCoord(seed, x1, y3, z0), ValCoord(seed, x2, y3, z0), ValCoord(seed, x3, y3, z0), xs),
+ ys),
+ CubicLerp(
+ CubicLerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), ValCoord(seed, x2, y0, z1), ValCoord(seed, x3, y0, z1), xs),
+ CubicLerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), ValCoord(seed, x2, y1, z1), ValCoord(seed, x3, y1, z1), xs),
+ CubicLerp(ValCoord(seed, x0, y2, z1), ValCoord(seed, x1, y2, z1), ValCoord(seed, x2, y2, z1), ValCoord(seed, x3, y2, z1), xs),
+ CubicLerp(ValCoord(seed, x0, y3, z1), ValCoord(seed, x1, y3, z1), ValCoord(seed, x2, y3, z1), ValCoord(seed, x3, y3, z1), xs),
+ ys),
+ CubicLerp(
+ CubicLerp(ValCoord(seed, x0, y0, z2), ValCoord(seed, x1, y0, z2), ValCoord(seed, x2, y0, z2), ValCoord(seed, x3, y0, z2), xs),
+ CubicLerp(ValCoord(seed, x0, y1, z2), ValCoord(seed, x1, y1, z2), ValCoord(seed, x2, y1, z2), ValCoord(seed, x3, y1, z2), xs),
+ CubicLerp(ValCoord(seed, x0, y2, z2), ValCoord(seed, x1, y2, z2), ValCoord(seed, x2, y2, z2), ValCoord(seed, x3, y2, z2), xs),
+ CubicLerp(ValCoord(seed, x0, y3, z2), ValCoord(seed, x1, y3, z2), ValCoord(seed, x2, y3, z2), ValCoord(seed, x3, y3, z2), xs),
+ ys),
+ CubicLerp(
+ CubicLerp(ValCoord(seed, x0, y0, z3), ValCoord(seed, x1, y0, z3), ValCoord(seed, x2, y0, z3), ValCoord(seed, x3, y0, z3), xs),
+ CubicLerp(ValCoord(seed, x0, y1, z3), ValCoord(seed, x1, y1, z3), ValCoord(seed, x2, y1, z3), ValCoord(seed, x3, y1, z3), xs),
+ CubicLerp(ValCoord(seed, x0, y2, z3), ValCoord(seed, x1, y2, z3), ValCoord(seed, x2, y2, z3), ValCoord(seed, x3, y2, z3), xs),
+ CubicLerp(ValCoord(seed, x0, y3, z3), ValCoord(seed, x1, y3, z3), ValCoord(seed, x2, y3, z3), ValCoord(seed, x3, y3, z3), xs),
+ ys),
+ zs) * (1 / (1.5f * 1.5f * 1.5f));
+ }
+
+
+ // Value Noise
+
+ private float SingleValue(int seed, FNLfloat x, FNLfloat y)
+ {
+ int x0 = FastFloor(x);
+ int y0 = FastFloor(y);
+
+ float xs = InterpHermite((float)(x - x0));
+ float ys = InterpHermite((float)(y - y0));
+
+ x0 *= PrimeX;
+ y0 *= PrimeY;
+ int x1 = x0 + PrimeX;
+ int y1 = y0 + PrimeY;
+
+ float xf0 = Lerp(ValCoord(seed, x0, y0), ValCoord(seed, x1, y0), xs);
+ float xf1 = Lerp(ValCoord(seed, x0, y1), ValCoord(seed, x1, y1), xs);
+
+ return Lerp(xf0, xf1, ys);
+ }
+
+ private float SingleValue(int seed, FNLfloat x, FNLfloat y, FNLfloat z)
+ {
+ int x0 = FastFloor(x);
+ int y0 = FastFloor(y);
+ int z0 = FastFloor(z);
+
+ float xs = InterpHermite((float)(x - x0));
+ float ys = InterpHermite((float)(y - y0));
+ float zs = InterpHermite((float)(z - z0));
+
+ x0 *= PrimeX;
+ y0 *= PrimeY;
+ z0 *= PrimeZ;
+ int x1 = x0 + PrimeX;
+ int y1 = y0 + PrimeY;
+ int z1 = z0 + PrimeZ;
+
+ float xf00 = Lerp(ValCoord(seed, x0, y0, z0), ValCoord(seed, x1, y0, z0), xs);
+ float xf10 = Lerp(ValCoord(seed, x0, y1, z0), ValCoord(seed, x1, y1, z0), xs);
+ float xf01 = Lerp(ValCoord(seed, x0, y0, z1), ValCoord(seed, x1, y0, z1), xs);
+ float xf11 = Lerp(ValCoord(seed, x0, y1, z1), ValCoord(seed, x1, y1, z1), xs);
+
+ float yf0 = Lerp(xf00, xf10, ys);
+ float yf1 = Lerp(xf01, xf11, ys);
+
+ return Lerp(yf0, yf1, zs);
+ }
+
+
+ // Domain Warp
+
+ private void DoSingleDomainWarp(int seed, float amp, float freq, FNLfloat x, FNLfloat y, ref FNLfloat xr, ref FNLfloat yr)
+ {
+ switch (mDomainWarpType)
+ {
+ case DomainWarpType.OpenSimplex2:
+ SingleDomainWarpSimplexGradient(seed, amp * 38.283687591552734375f, freq, x, y, ref xr, ref yr, false);
+ break;
+ case DomainWarpType.OpenSimplex2Reduced:
+ SingleDomainWarpSimplexGradient(seed, amp * 16.0f, freq, x, y, ref xr, ref yr, true);
+ break;
+ case DomainWarpType.BasicGrid:
+ SingleDomainWarpBasicGrid(seed, amp, freq, x, y, ref xr, ref yr);
+ break;
+ }
+ }
+
+ private void DoSingleDomainWarp(int seed, float amp, float freq, FNLfloat x, FNLfloat y, FNLfloat z, ref FNLfloat xr, ref FNLfloat yr, ref FNLfloat zr)
+ {
+ switch (mDomainWarpType)
+ {
+ case DomainWarpType.OpenSimplex2:
+ SingleDomainWarpOpenSimplex2Gradient(seed, amp * 32.69428253173828125f, freq, x, y, z, ref xr, ref yr, ref zr, false);
+ break;
+ case DomainWarpType.OpenSimplex2Reduced:
+ SingleDomainWarpOpenSimplex2Gradient(seed, amp * 7.71604938271605f, freq, x, y, z, ref xr, ref yr, ref zr, true);
+ break;
+ case DomainWarpType.BasicGrid:
+ SingleDomainWarpBasicGrid(seed, amp, freq, x, y, z, ref xr, ref yr, ref zr);
+ break;
+ }
+ }
+
+
+ // Domain Warp Single Wrapper
+
+ private void DomainWarpSingle(ref FNLfloat x, ref FNLfloat y)
+ {
+ int seed = mSeed;
+ float amp = mDomainWarpAmp * mFractalBounding;
+ float freq = mFrequency;
+
+ FNLfloat xs = x;
+ FNLfloat ys = y;
+ TransformDomainWarpCoordinate(ref xs, ref ys);
+
+ DoSingleDomainWarp(seed, amp, freq, xs, ys, ref x, ref y);
+ }
+
+ private void DomainWarpSingle(ref FNLfloat x, ref FNLfloat y, ref FNLfloat z)
+ {
+ int seed = mSeed;
+ float amp = mDomainWarpAmp * mFractalBounding;
+ float freq = mFrequency;
+
+ FNLfloat xs = x;
+ FNLfloat ys = y;
+ FNLfloat zs = z;
+ TransformDomainWarpCoordinate(ref xs, ref ys, ref zs);
+
+ DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, ref x, ref y, ref z);
+ }
+
+
+ // Domain Warp Fractal Progressive
+
+ private void DomainWarpFractalProgressive(ref FNLfloat x, ref FNLfloat y)
+ {
+ int seed = mSeed;
+ float amp = mDomainWarpAmp * mFractalBounding;
+ float freq = mFrequency;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ FNLfloat xs = x;
+ FNLfloat ys = y;
+ TransformDomainWarpCoordinate(ref xs, ref ys);
+
+ DoSingleDomainWarp(seed, amp, freq, xs, ys, ref x, ref y);
+
+ seed++;
+ amp *= mGain;
+ freq *= mLacunarity;
+ }
+ }
+
+ private void DomainWarpFractalProgressive(ref FNLfloat x, ref FNLfloat y, ref FNLfloat z)
+ {
+ int seed = mSeed;
+ float amp = mDomainWarpAmp * mFractalBounding;
+ float freq = mFrequency;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ FNLfloat xs = x;
+ FNLfloat ys = y;
+ FNLfloat zs = z;
+ TransformDomainWarpCoordinate(ref xs, ref ys, ref zs);
+
+ DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, ref x, ref y, ref z);
+
+ seed++;
+ amp *= mGain;
+ freq *= mLacunarity;
+ }
+ }
+
+
+ // Domain Warp Fractal Independant
+ private void DomainWarpFractalIndependent(ref FNLfloat x, ref FNLfloat y)
+ {
+ FNLfloat xs = x;
+ FNLfloat ys = y;
+ TransformDomainWarpCoordinate(ref xs, ref ys);
+
+ int seed = mSeed;
+ float amp = mDomainWarpAmp * mFractalBounding;
+ float freq = mFrequency;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ DoSingleDomainWarp(seed, amp, freq, xs, ys, ref x, ref y);
+
+ seed++;
+ amp *= mGain;
+ freq *= mLacunarity;
+ }
+ }
+
+ private void DomainWarpFractalIndependent(ref FNLfloat x, ref FNLfloat y, ref FNLfloat z)
+ {
+ FNLfloat xs = x;
+ FNLfloat ys = y;
+ FNLfloat zs = z;
+ TransformDomainWarpCoordinate(ref xs, ref ys, ref zs);
+
+ int seed = mSeed;
+ float amp = mDomainWarpAmp * mFractalBounding;
+ float freq = mFrequency;
+
+ for (int i = 0; i < mOctaves; i++)
+ {
+ DoSingleDomainWarp(seed, amp, freq, xs, ys, zs, ref x, ref y, ref z);
+
+ seed++;
+ amp *= mGain;
+ freq *= mLacunarity;
+ }
+ }
+
+
+ // Domain Warp Basic Grid
+
+ private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNLfloat x, FNLfloat y, ref FNLfloat xr, ref FNLfloat yr)
+ {
+ FNLfloat xf = x * frequency;
+ FNLfloat yf = y * frequency;
+
+ int x0 = FastFloor(xf);
+ int y0 = FastFloor(yf);
+
+ float xs = InterpHermite((float)(xf - x0));
+ float ys = InterpHermite((float)(yf - y0));
+
+ x0 *= PrimeX;
+ y0 *= PrimeY;
+ int x1 = x0 + PrimeX;
+ int y1 = y0 + PrimeY;
+
+ int hash0 = Hash(seed, x0, y0) & (255 << 1);
+ int hash1 = Hash(seed, x1, y0) & (255 << 1);
+
+ float lx0x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs);
+ float ly0x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs);
+
+ hash0 = Hash(seed, x0, y1) & (255 << 1);
+ hash1 = Hash(seed, x1, y1) & (255 << 1);
+
+ float lx1x = Lerp(RandVecs2D[hash0], RandVecs2D[hash1], xs);
+ float ly1x = Lerp(RandVecs2D[hash0 | 1], RandVecs2D[hash1 | 1], xs);
+
+ xr += Lerp(lx0x, lx1x, ys) * warpAmp;
+ yr += Lerp(ly0x, ly1x, ys) * warpAmp;
+ }
+
+ private void SingleDomainWarpBasicGrid(int seed, float warpAmp, float frequency, FNLfloat x, FNLfloat y, FNLfloat z, ref FNLfloat xr, ref FNLfloat yr, ref FNLfloat zr)
+ {
+ FNLfloat xf = x * frequency;
+ FNLfloat yf = y * frequency;
+ FNLfloat zf = z * frequency;
+
+ int x0 = FastFloor(xf);
+ int y0 = FastFloor(yf);
+ int z0 = FastFloor(zf);
+
+ float xs = InterpHermite((float)(xf - x0));
+ float ys = InterpHermite((float)(yf - y0));
+ float zs = InterpHermite((float)(zf - z0));
+
+ x0 *= PrimeX;
+ y0 *= PrimeY;
+ z0 *= PrimeZ;
+ int x1 = x0 + PrimeX;
+ int y1 = y0 + PrimeY;
+ int z1 = z0 + PrimeZ;
+
+ int hash0 = Hash(seed, x0, y0, z0) & (255 << 2);
+ int hash1 = Hash(seed, x1, y0, z0) & (255 << 2);
+
+ float lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs);
+ float ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs);
+ float lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs);
+
+ hash0 = Hash(seed, x0, y1, z0) & (255 << 2);
+ hash1 = Hash(seed, x1, y1, z0) & (255 << 2);
+
+ float lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs);
+ float ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs);
+ float lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs);
+
+ float lx0y = Lerp(lx0x, lx1x, ys);
+ float ly0y = Lerp(ly0x, ly1x, ys);
+ float lz0y = Lerp(lz0x, lz1x, ys);
+
+ hash0 = Hash(seed, x0, y0, z1) & (255 << 2);
+ hash1 = Hash(seed, x1, y0, z1) & (255 << 2);
+
+ lx0x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs);
+ ly0x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs);
+ lz0x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs);
+
+ hash0 = Hash(seed, x0, y1, z1) & (255 << 2);
+ hash1 = Hash(seed, x1, y1, z1) & (255 << 2);
+
+ lx1x = Lerp(RandVecs3D[hash0], RandVecs3D[hash1], xs);
+ ly1x = Lerp(RandVecs3D[hash0 | 1], RandVecs3D[hash1 | 1], xs);
+ lz1x = Lerp(RandVecs3D[hash0 | 2], RandVecs3D[hash1 | 2], xs);
+
+ xr += Lerp(lx0y, Lerp(lx0x, lx1x, ys), zs) * warpAmp;
+ yr += Lerp(ly0y, Lerp(ly0x, ly1x, ys), zs) * warpAmp;
+ zr += Lerp(lz0y, Lerp(lz0x, lz1x, ys), zs) * warpAmp;
+ }
+
+
+ // Domain Warp Simplex/OpenSimplex2
+ private void SingleDomainWarpSimplexGradient(int seed, float warpAmp, float frequency, FNLfloat x, FNLfloat y, ref FNLfloat xr, ref FNLfloat yr, bool outGradOnly)
+ {
+ const float SQRT3 = 1.7320508075688772935274463415059f;
+ const float G2 = (3 - SQRT3) / 6;
+
+ x *= frequency;
+ y *= frequency;
+
+ /*
+ * --- Skew moved to TransformNoiseCoordinate method ---
+ * const FNfloat F2 = 0.5f * (SQRT3 - 1);
+ * FNfloat s = (x + y) * F2;
+ * x += s; y += s;
+ */
+
+ int i = FastFloor(x);
+ int j = FastFloor(y);
+ float xi = (float)(x - i);
+ float yi = (float)(y - j);
+
+ float t = (xi + yi) * G2;
+ float x0 = (float)(xi - t);
+ float y0 = (float)(yi - t);
+
+ i *= PrimeX;
+ j *= PrimeY;
+
+ float vx, vy;
+ vx = vy = 0;
+
+ float a = 0.5f - x0 * x0 - y0 * y0;
+ if (a > 0)
+ {
+ float aaaa = (a * a) * (a * a);
+ float xo, yo;
+ if (outGradOnly)
+ GradCoordOut(seed, i, j, out xo, out yo);
+ else
+ GradCoordDual(seed, i, j, x0, y0, out xo, out yo);
+ vx += aaaa * xo;
+ vy += aaaa * yo;
+ }
+
+ float c = (float)(2 * (1 - 2 * G2) * (1 / G2 - 2)) * t + ((float)(-2 * (1 - 2 * G2) * (1 - 2 * G2)) + a);
+ if (c > 0)
+ {
+ float x2 = x0 + (2 * (float)G2 - 1);
+ float y2 = y0 + (2 * (float)G2 - 1);
+ float cccc = (c * c) * (c * c);
+ float xo, yo;
+ if (outGradOnly)
+ GradCoordOut(seed, i + PrimeX, j + PrimeY, out xo, out yo);
+ else
+ GradCoordDual(seed, i + PrimeX, j + PrimeY, x2, y2, out xo, out yo);
+ vx += cccc * xo;
+ vy += cccc * yo;
+ }
+
+ if (y0 > x0)
+ {
+ float x1 = x0 + (float)G2;
+ float y1 = y0 + ((float)G2 - 1);
+ float b = 0.5f - x1 * x1 - y1 * y1;
+ if (b > 0)
+ {
+ float bbbb = (b * b) * (b * b);
+ float xo, yo;
+ if (outGradOnly)
+ GradCoordOut(seed, i, j + PrimeY, out xo, out yo);
+ else
+ GradCoordDual(seed, i, j + PrimeY, x1, y1, out xo, out yo);
+ vx += bbbb * xo;
+ vy += bbbb * yo;
+ }
+ }
+ else
+ {
+ float x1 = x0 + ((float)G2 - 1);
+ float y1 = y0 + (float)G2;
+ float b = 0.5f - x1 * x1 - y1 * y1;
+ if (b > 0)
+ {
+ float bbbb = (b * b) * (b * b);
+ float xo, yo;
+ if (outGradOnly)
+ GradCoordOut(seed, i + PrimeX, j, out xo, out yo);
+ else
+ GradCoordDual(seed, i + PrimeX, j, x1, y1, out xo, out yo);
+ vx += bbbb * xo;
+ vy += bbbb * yo;
+ }
+ }
+
+ xr += vx * warpAmp;
+ yr += vy * warpAmp;
+ }
+
+ private void SingleDomainWarpOpenSimplex2Gradient(int seed, float warpAmp, float frequency, FNLfloat x, FNLfloat y, FNLfloat z, ref FNLfloat xr, ref FNLfloat yr, ref FNLfloat zr, bool outGradOnly)
+ {
+ x *= frequency;
+ y *= frequency;
+ z *= frequency;
+
+ /*
+ * --- Rotation moved to TransformDomainWarpCoordinate method ---
+ * const FNfloat R3 = (FNfloat)(2.0 / 3.0);
+ * FNfloat r = (x + y + z) * R3; // Rotation, not skew
+ * x = r - x; y = r - y; z = r - z;
+ */
+
+ int i = FastRound(x);
+ int j = FastRound(y);
+ int k = FastRound(z);
+ float x0 = (float)x - i;
+ float y0 = (float)y - j;
+ float z0 = (float)z - k;
+
+ int xNSign = (int)(-x0 - 1.0f) | 1;
+ int yNSign = (int)(-y0 - 1.0f) | 1;
+ int zNSign = (int)(-z0 - 1.0f) | 1;
+
+ float ax0 = xNSign * -x0;
+ float ay0 = yNSign * -y0;
+ float az0 = zNSign * -z0;
+
+ i *= PrimeX;
+ j *= PrimeY;
+ k *= PrimeZ;
+
+ float vx, vy, vz;
+ vx = vy = vz = 0;
+
+ float a = (0.6f - x0 * x0) - (y0 * y0 + z0 * z0);
+ for (int l = 0; ; l++)
+ {
+ if (a > 0)
+ {
+ float aaaa = (a * a) * (a * a);
+ float xo, yo, zo;
+ if (outGradOnly)
+ GradCoordOut(seed, i, j, k, out xo, out yo, out zo);
+ else
+ GradCoordDual(seed, i, j, k, x0, y0, z0, out xo, out yo, out zo);
+ vx += aaaa * xo;
+ vy += aaaa * yo;
+ vz += aaaa * zo;
+ }
+
+ float b = a;
+ int i1 = i;
+ int j1 = j;
+ int k1 = k;
+ float x1 = x0;
+ float y1 = y0;
+ float z1 = z0;
+
+ if (ax0 >= ay0 && ax0 >= az0)
+ {
+ x1 += xNSign;
+ b = b + ax0 + ax0;
+ i1 -= xNSign * PrimeX;
+ }
+ else if (ay0 > ax0 && ay0 >= az0)
+ {
+ y1 += yNSign;
+ b = b + ay0 + ay0;
+ j1 -= yNSign * PrimeY;
+ }
+ else
+ {
+ z1 += zNSign;
+ b = b + az0 + az0;
+ k1 -= zNSign * PrimeZ;
+ }
+
+ if (b > 1)
+ {
+ b -= 1;
+ float bbbb = (b * b) * (b * b);
+ float xo, yo, zo;
+ if (outGradOnly)
+ GradCoordOut(seed, i1, j1, k1, out xo, out yo, out zo);
+ else
+ GradCoordDual(seed, i1, j1, k1, x1, y1, z1, out xo, out yo, out zo);
+ vx += bbbb * xo;
+ vy += bbbb * yo;
+ vz += bbbb * zo;
+ }
+
+ if (l == 1) break;
+
+ ax0 = 0.5f - ax0;
+ ay0 = 0.5f - ay0;
+ az0 = 0.5f - az0;
+
+ x0 = xNSign * ax0;
+ y0 = yNSign * ay0;
+ z0 = zNSign * az0;
+
+ a += (0.75f - ax0) - (ay0 + az0);
+
+ i += (xNSign >> 1) & PrimeX;
+ j += (yNSign >> 1) & PrimeY;
+ k += (zNSign >> 1) & PrimeZ;
+
+ xNSign = -xNSign;
+ yNSign = -yNSign;
+ zNSign = -zNSign;
+
+ seed += 1293373;
+ }
+
+ xr += vx * warpAmp;
+ yr += vy * warpAmp;
+ zr += vz * warpAmp;
+ }
+} \ No newline at end of file
diff --git a/source/resources/Resources.cs b/source/resources/Resources.cs
new file mode 100644
index 0000000..d200764
--- /dev/null
+++ b/source/resources/Resources.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using Celesteia.Resources.Management;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework.Content;
+
+namespace Celesteia.Resources {
+ public static class ResourceManager {
+ public static ItemManager Items = new ItemManager();
+ public static BlockManager Blocks = new BlockManager();
+ public static RecipeManager Recipes = new RecipeManager();
+ public static EntityManager Entities = new EntityManager();
+ public static FontTypes Fonts = new FontTypes();
+ public static SkyboxAssets Skybox = new SkyboxAssets();
+
+ public const float SPRITE_SCALING = 0.125f;
+ public const int INVERSE_SPRITE_SCALING = 8;
+
+ public static void AddCollection(IResourceCollection collection) {
+ Blocks.AddCollection(collection);
+ Items.AddCollection(collection);
+ Recipes.AddCollection(collection);
+ Entities.AddCollection(collection);
+ }
+
+ public static void LoadContent(ContentManager content) {
+ Blocks.LoadContent(content);
+ Items.LoadContent(content);
+ Recipes.LoadContent(content);
+ Entities.LoadContent(content);
+
+ Fonts.LoadContent(content);
+ Skybox.LoadContent(content);
+ }
+ }
+
+ public struct NamespacedKey {
+ public readonly string Namespace;
+ public readonly string Index;
+
+ public NamespacedKey(string ns, string index) {
+ Namespace = ns;
+ Index = index;
+ }
+
+ public static NamespacedKey Base(string index) {
+ return new NamespacedKey("celesteia", index);
+ }
+
+ public string Qualify() {
+ return $"{Namespace}:{Index}";
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is NamespacedKey && ((NamespacedKey)obj).Namespace == Namespace && ((NamespacedKey)obj).Index == Index;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+
+ public interface IResourceType {
+ public byte GetID();
+ public void SetID(byte id);
+ }
+
+ public interface IResourceManager {
+ public void AddCollection(IResourceCollection collection);
+ public IResourceType GetResource(NamespacedKey namespacedKey);
+ }
+
+ public interface IResourceCollection {
+ public Dictionary<NamespacedKey, BlockType> GetBlocks();
+ public Dictionary<NamespacedKey, ItemType> GetItems();
+ public Dictionary<NamespacedKey, Recipe> GetRecipes();
+ public Dictionary<NamespacedKey, EntityType> GetEntities();
+ public NamespacedKey GetKey(string index);
+ }
+} \ No newline at end of file
diff --git a/source/resources/collections/BaseCollection.cs b/source/resources/collections/BaseCollection.cs
new file mode 100644
index 0000000..2af029e
--- /dev/null
+++ b/source/resources/collections/BaseCollection.cs
@@ -0,0 +1,207 @@
+using System.Collections.Generic;
+using Celesteia.Game.Components;
+using Celesteia.Game.ECS;
+using Celesteia.Game.Items;
+using Celesteia.Graphics.Lighting;
+using Celesteia.Resources.Types;
+using Celesteia.Resources.Types.Builders;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+/*
+ A collection of resources for the base game.
+*/
+namespace Celesteia.Resources.Collections {
+ public class BaseCollection : IResourceCollection
+ {
+ public NamespacedKey GetKey(string index) => NamespacedKey.Base(index);
+
+ private ContentManager _content;
+ public BaseCollection(ContentManager Content) {
+ _content = Content;
+ }
+
+
+ public Dictionary<NamespacedKey, BlockType> GetBlocks() => LoadBlocks();
+ private Dictionary<NamespacedKey, BlockType> blocks;
+ private Dictionary<NamespacedKey, BlockType> LoadBlocks(int pixelSize = 8) {
+ if (blocks != null) return blocks;
+
+ void AddBlock(string index, BlockType type) => blocks.Add(GetKey(index), type);
+
+ TextureAtlas _atlas = TextureAtlas.Create("blocks", _content.Load<Texture2D>("sprites/blocks"), pixelSize, pixelSize);
+ BlockTypeBuilder builder = new BlockTypeBuilder(_atlas);
+
+ blocks = new Dictionary<NamespacedKey, BlockType>();
+ AddBlock("air", builder.WithName("Air").Invisible().Get());
+ AddBlock("grown_soil", builder.WithName("Grown Soil").Full().Frames(0).Properties(
+ strength: 5,
+ drop: GetKey("soil")
+ ).Get());
+ AddBlock("soil", builder.WithName("Soil").Full().Frames(1).Properties(
+ strength: 5,
+ drop: GetKey("soil")
+ ).Get());
+ AddBlock("stone", builder.WithName("Stone").Full().Frames(2).Properties(
+ strength: 7,
+ drop: GetKey("stone")
+ ).Get());
+ AddBlock("deepstone", builder.WithName("Deepstone").Full().Frames(3).Properties(
+ strength: -1,
+ drop: GetKey("deepstone")
+ ).Get());
+ AddBlock("log", builder.WithName("Wooden Log").Full().Frames(10).Properties(
+ strength: 2,
+ drop: GetKey("log")
+ ).Get());
+ AddBlock("leaves", builder.WithName("Leaves").Walkthrough().Frames(11).Properties(
+ translucent: true,
+ strength: 1,
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+ AddBlock("iron_ore", builder.WithName("Iron Ore").Full().Frames(8).Properties(
+ strength: 15,
+ drop: GetKey("iron_ore")
+ ).Get());
+ AddBlock("copper_ore", builder.WithName("Copper Ore").Full().Frames(7).Properties(
+ strength: 10,
+ drop: GetKey("copper_ore")
+ ).Get());
+ AddBlock("coal_ore", builder.WithName("Coal Ore").Full().Frames(14).Properties(
+ strength: 10,
+ drop: GetKey("coal")
+ ).Get());
+ AddBlock("wooden_planks", builder.WithName("Wooden Planks").Full().Frames(4).Properties(
+ strength: 4,
+ drop: GetKey("wooden_planks")
+ ).Get());
+ AddBlock("torch", builder.WithName("Torch").Walkthrough().Frames(9).Properties(
+ translucent: true,
+ strength: 1,
+ drop: GetKey("wooden_torch"),
+ light: new BlockLightProperties(LightColor.white, 6, false)
+ ).Get());
+ AddBlock("stone_bricks", builder.WithName("Stone Bricks").Frames(6).Full().Properties(
+ strength: 7,
+ drop: GetKey("stone_bricks")
+ ).Get());
+ AddBlock("unyx_bricks", builder.WithName("Unyx Bricks").Frames(12).Full().Properties(
+ strength: 10,
+ drop: GetKey("unyx_bricks")
+ ).Get());
+ AddBlock("unyx_eyestone", builder.WithName("Unyx Eyestone").Frames(13).Full().Properties(
+ strength: 10,
+ drop: GetKey("unyx_eyestone"),
+ light: new BlockLightProperties(new LightColor(230f, 74f, 255f), 5, true)
+ ).Get());
+ AddBlock("scorched_soil", builder.WithName("Scorched Soil").Frames(15).Full().Properties(
+ strength: -1
+ ).Get());
+ AddBlock("blue_flower", builder.WithName("Morning Stars").Frames(16).Walkthrough().Properties(
+ strength: 1,
+ drop: GetKey("blue_flower_bundle"),
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+ AddBlock("red_flower", builder.WithName("Red Tears").Frames(17).Walkthrough().Properties(
+ strength: 1,
+ drop: GetKey("red_flower_bundle"),
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+ AddBlock("violet_flower", builder.WithName("Colupria").Frames(18).Walkthrough().Properties(
+ strength: 1,
+ drop: GetKey("violet_flower_bundle"),
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+ AddBlock("grass", builder.WithName("Grass").Frames(19).Walkthrough().Properties(
+ strength: 1,
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+ AddBlock("crashed_capsule_base", builder.WithName("Crashed Capsule").UniqueFrames(
+ TextureAtlas.Create("cc", _content.Load<Texture2D>("sprites/crashed_capsule"), pixelSize * 3, pixelSize * 3), 0, 1,
+ new Vector2(pixelSize * 1, pixelSize * 2)
+ ).Walkthrough().Properties(
+ translucent: true,
+ strength: -1,
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+ AddBlock("crashed_capsule_frame", builder.WithName("Crashed Capsule").Invisible().Properties(
+ translucent: true,
+ strength: -1,
+ light: new BlockLightProperties(LightColor.black, 0, false)
+ ).Get());
+
+ return blocks;
+ }
+
+ public Dictionary<NamespacedKey, ItemType> GetItems() => LoadItems();
+ private Dictionary<NamespacedKey, ItemType> items;
+ private Dictionary<NamespacedKey, ItemType> LoadItems(int pixelSize = 16) {
+ if (items != null) return items;
+
+ void AddItem(string index, ItemType type) => items.Add(GetKey(index), type);
+
+ TextureAtlas _atlas = TextureAtlas.Create("items", _content.Load<Texture2D>("sprites/items"), pixelSize, pixelSize);
+ ItemTypeBuilder builder = new ItemTypeBuilder(_atlas);
+
+ items = new Dictionary<NamespacedKey, ItemType>();
+ if (blocks != null) {
+ foreach (KeyValuePair<NamespacedKey, BlockType> pair in blocks) {
+ if (pair.Value.Frames == null) continue;
+ AddItem(pair.Key.Index, builder.WithName(pair.Value.Name).Block(pair.Key).Frame(pair.Value.Frames.GetFrame(0).GetRegion()).Get());
+ }
+ }
+
+ AddItem("iron_pickaxe", builder.WithName("Iron Pickaxe").Pickaxe(4).Frame(0).Get());
+ AddItem("wooden_log", builder.WithName("Wooden Log").Frame(1).Block(NamespacedKey.Base("log")).Get());
+ AddItem("coal", builder.WithName("Coal Lump").Frame(2).Get());
+ AddItem("plank", builder.WithName("Plank").Frame(3).Get());
+ AddItem("copper_ingot", builder.WithName("Copper Ingot").Frame(4).Get());
+ AddItem("iron_ingot", builder.WithName("Iron Ingot").Frame(5).Get());
+ AddItem("fuel_tank", builder.WithName("Fuel Tank").Frame(6).Upgrade(EntityAttribute.JumpFuel, 0.5f, 5f).Get());
+ AddItem("wooden_torch", builder.WithName("Wooden Torch").Template(new ItemTypeTemplate(1000, true))
+ .Frame(7).Actions(new TorchItemActions(NamespacedKey.Base("torch"))).Get());
+ AddItem("blue_flower_bundle", builder.WithName("Morning Stars").Template(new ItemTypeTemplate(1000, true))
+ .Frame(10).Actions(new FoliageItemActions(NamespacedKey.Base("blue_flower"))).Get());
+ AddItem("red_flower_bundle", builder.WithName("Red Tears").Template(new ItemTypeTemplate(1000, true))
+ .Frame(11).Actions(new FoliageItemActions(NamespacedKey.Base("red_flower"))).Get());
+ AddItem("violet_flower_bundle", builder.WithName("Colupria").Template(new ItemTypeTemplate(1000, true))
+ .Frame(12).Actions(new FoliageItemActions(NamespacedKey.Base("violet_flower"))).Get());
+
+ return items;
+ }
+
+ public Dictionary<NamespacedKey, Recipe> GetRecipes() => LoadRecipes();
+ private Dictionary<NamespacedKey, Recipe> recipes;
+ private Dictionary<NamespacedKey, Recipe> LoadRecipes() {
+ if (recipes != null) return recipes;
+
+ void AddRecipe(string index, Recipe type) => recipes.Add(GetKey(index), type);
+
+ recipes = new Dictionary<NamespacedKey, Recipe>();
+ AddRecipe("plank_ingredient", new Recipe(new Part(GetKey("plank"), 4), new Part(GetKey("log"), 1)));
+ AddRecipe("plank_block", new Recipe(new Part(GetKey("wooden_planks"), 1), new Part(GetKey("plank"), 2)));
+ AddRecipe("copper_smelt", new Recipe(new Part(GetKey("copper_ingot"), 1), new Part(GetKey("copper_ore"), 1)));
+ AddRecipe("iron_smelt", new Recipe(new Part(GetKey("iron_ingot"), 1), new Part(GetKey("iron_ore"), 1)));
+ AddRecipe("fuel_tank", new Recipe(new Part(GetKey("fuel_tank"), 1), new Part(GetKey("iron_ingot"), 10), new Part(GetKey("copper_ingot"), 5)));
+ AddRecipe("torches", new Recipe(new Part(GetKey("wooden_torch"), 4), new Part(GetKey("plank"), 1), new Part(GetKey("coal"), 1)));
+ AddRecipe("stone_brick", new Recipe(new Part(GetKey("stone_bricks"), 4), new Part(GetKey("stone"), 4)));
+
+ return recipes;
+ }
+
+ public Dictionary<NamespacedKey, EntityType> GetEntities() => LoadEntities();
+ private Dictionary<NamespacedKey, EntityType> entities;
+ private Dictionary<NamespacedKey, EntityType> LoadEntities() {
+ if (entities != null) return entities;
+
+ void AddEntity(string index, EntityType type) => entities.Add(GetKey(index), type);
+
+ entities = new Dictionary<NamespacedKey, EntityType>();
+ AddEntity("player", new EntityType((e) => EntityFactory.BuildPlayer(e, _content.Load<Texture2D>("sprites/entities/player/astronaut"))));
+
+ return entities;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/management/BlockManager.cs b/source/resources/management/BlockManager.cs
new file mode 100644
index 0000000..2271c0c
--- /dev/null
+++ b/source/resources/management/BlockManager.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Celesteia.Resources.Sprites;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Management {
+ public abstract class BlockSpriteProperties {
+ public const int SIZE = 8;
+ }
+
+ public class BlockManager : IResourceManager {
+ private List<BlockType> Types;
+ private BlockType[] BakedTypes;
+ private Dictionary<string, byte> keys = new Dictionary<string, byte>();
+
+ public BlockFrames BreakAnimation;
+ public BlockFrames Selection;
+
+ private void LoadBreakingAnimation(ContentManager Content) {
+ Debug.WriteLine($"Loading block break animation...");
+ BreakAnimation = new BlockFrames(TextureAtlas.Create("blockbreak", Content.Load<Texture2D>("sprites/blockbreak"),
+ BlockSpriteProperties.SIZE,
+ BlockSpriteProperties.SIZE
+ ), 0, 3);
+ }
+
+ private void LoadSelector(ContentManager Content) {
+ Debug.WriteLine($"Loading block selector...");
+ Selection = new BlockFrames(TextureAtlas.Create("selection", Content.Load<Texture2D>("sprites/blockselection"),
+ BlockSpriteProperties.SIZE,
+ BlockSpriteProperties.SIZE
+ ), 0, 1);
+ }
+
+ private List<IResourceCollection> _collections = new List<IResourceCollection>();
+ public void AddCollection(IResourceCollection collection) => _collections.Add(collection);
+
+ public void LoadContent(ContentManager Content) {
+ LoadBreakingAnimation(Content);
+ LoadSelector(Content);
+
+ Debug.WriteLine($"Loading block types...");
+
+ Types = new List<BlockType>();
+
+ foreach (IResourceCollection collection in _collections)
+ LoadCollection(collection);
+
+ BakedTypes = Types.ToArray();
+ }
+
+ private void LoadCollection(IResourceCollection collection) {
+ foreach (NamespacedKey key in collection.GetBlocks().Keys) {
+ AddType(key, collection.GetBlocks()[key]);
+ }
+ }
+
+ private byte next = 0;
+ private void AddType(NamespacedKey key, BlockType type) {
+ type.SetID(next++);
+ keys.Add(key.Qualify(), type.GetID());
+
+ Types.Add(type);
+ }
+
+ public BlockType GetBlock(byte id) => BakedTypes[id];
+
+ public IResourceType GetResource(NamespacedKey key) {
+ if (!keys.ContainsKey(key.Qualify())) throw new NullReferenceException();
+ return BakedTypes[keys[key.Qualify()]];
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/management/EntityManager.cs b/source/resources/management/EntityManager.cs
new file mode 100644
index 0000000..74a4dbe
--- /dev/null
+++ b/source/resources/management/EntityManager.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework.Content;
+
+namespace Celesteia.Resources.Management {
+ public class EntityManager : IResourceManager {
+ public List<EntityType> Types;
+ private EntityType[] BakedTypes;
+ private Dictionary<string, byte> keys = new Dictionary<string, byte>();
+
+ private List<IResourceCollection> _collections = new List<IResourceCollection>();
+ public void AddCollection(IResourceCollection collection) => _collections.Add(collection);
+
+ public void LoadContent(ContentManager Content) {
+ Debug.WriteLine($"Loading entity types...");
+
+ Types = new List<EntityType>();
+
+ foreach (IResourceCollection collection in _collections)
+ LoadCollection(collection);
+
+ BakedTypes = Types.ToArray();
+ }
+
+ private void LoadCollection(IResourceCollection collection) {
+ foreach (NamespacedKey key in collection.GetEntities().Keys) {
+ AddType(key, collection.GetEntities()[key]);
+ }
+ }
+
+ private byte next = 0;
+ private void AddType(NamespacedKey key, EntityType type) {
+ type.SetID(next++);
+ keys.Add(key.Qualify(), type.GetID());
+
+ Types.Add(type);
+ }
+
+ public IResourceType GetResource(NamespacedKey key) {
+ if (!keys.ContainsKey(key.Qualify())) throw new NullReferenceException();
+ return BakedTypes[keys[key.Qualify()]];
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/management/FontManager.cs b/source/resources/management/FontManager.cs
new file mode 100644
index 0000000..482f25e
--- /dev/null
+++ b/source/resources/management/FontManager.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.Resources.Management {
+ public abstract class FontProperties {
+ public const int STANDARD_SIZE = 12;
+ }
+
+ public class FontTypes {
+ public FontType DEFAULT { get; private set; }
+ private List<FontType> Types;
+
+ public void LoadContent(ContentManager Content) {
+ Debug.WriteLine($"Loading fonts...");
+
+ Types = new List<FontType>();
+
+ Types.Add(DEFAULT = new FontType("Hobo", Content.Load<SpriteFont>("Hobo")));
+ }
+
+ public FontType GetFontType(string name) {
+ return Types.Find(x => x.Name == name);
+ }
+ }
+
+ public class FontType {
+ public readonly string Name;
+ public readonly SpriteFont Font;
+
+ public FontType(string name, SpriteFont font) {
+ Name = name;
+ Font = font;
+
+ Debug.WriteLine($" Font '{name}' loaded.");
+ }
+
+ public float Scale(float targetFontSize) {
+ return targetFontSize / FontProperties.STANDARD_SIZE;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/management/ItemManager.cs b/source/resources/management/ItemManager.cs
new file mode 100644
index 0000000..3970d7d
--- /dev/null
+++ b/source/resources/management/ItemManager.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework.Content;
+
+namespace Celesteia.Resources.Management {
+
+ public class ItemManager : IResourceManager {
+ private List<ItemType> Types;
+ private ItemType[] BakedTypes;
+ private Dictionary<string, byte> keys = new Dictionary<string, byte>();
+
+ private List<IResourceCollection> _collections = new List<IResourceCollection>();
+ public void AddCollection(IResourceCollection collection) => _collections.Add(collection);
+
+ public void LoadContent(ContentManager Content) {
+ Debug.WriteLine($"Loading item types...");
+
+ Types = new List<ItemType>();
+
+ foreach (IResourceCollection collection in _collections)
+ LoadCollection(collection);
+
+ BakedTypes = Types.ToArray();
+ }
+
+ private void LoadCollection(IResourceCollection collection) {
+ foreach (NamespacedKey key in collection.GetItems().Keys) {
+ AddType(key, collection.GetItems()[key]);
+ }
+ }
+
+ private byte next = 0;
+ private void AddType(NamespacedKey key, ItemType type) {
+ type.SetID(next++);
+ keys.Add(key.Qualify(), type.GetID());
+
+ Types.Add(type);
+ }
+
+ public IResourceType GetResource(NamespacedKey key) {
+ if (!keys.ContainsKey(key.Qualify())) throw new NullReferenceException();
+ return BakedTypes[keys[key.Qualify()]];
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/management/RecipeManager.cs b/source/resources/management/RecipeManager.cs
new file mode 100644
index 0000000..fb6df07
--- /dev/null
+++ b/source/resources/management/RecipeManager.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework.Content;
+
+namespace Celesteia.Resources.Management {
+ public class RecipeManager : IResourceManager {
+ public List<Recipe> Recipes;
+
+ private List<IResourceCollection> _collections = new List<IResourceCollection>();
+ public void AddCollection(IResourceCollection collection) => _collections.Add(collection);
+
+ public void LoadContent(ContentManager Content) {
+ Debug.WriteLine($"Loading crafting recipes...");
+
+ Recipes = new List<Recipe>();
+
+ foreach (IResourceCollection collection in _collections)
+ LoadCollection(collection);
+ }
+
+ private void LoadCollection(IResourceCollection collection) {
+ foreach (NamespacedKey key in collection.GetRecipes().Keys) {
+ AddType(collection.GetRecipes()[key]);
+ }
+ }
+
+ private void AddType(Recipe recipe) {
+ Recipes.Add(recipe);
+ }
+
+ public IResourceType GetResource(NamespacedKey namespacedKey) => null;
+ }
+} \ No newline at end of file
diff --git a/source/resources/management/SkyboxAssets.cs b/source/resources/management/SkyboxAssets.cs
new file mode 100644
index 0000000..049c2a5
--- /dev/null
+++ b/source/resources/management/SkyboxAssets.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using Celesteia.Resources.Sprites;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Management {
+ public class SkyboxAssets {
+ private List<SkyboxAsset> Assets;
+
+ public void LoadContent(ContentManager Content) {
+ Debug.WriteLine($"Loading skybox assets...");
+
+ Assets = new List<SkyboxAsset>();
+
+ Assets.Add(new SkyboxAsset(0, "stars", TextureAtlas.Create("stars", Content.Load<Texture2D>("sprites/skybox/stars2"), 1024, 1024), 1024, 0));
+ Assets.Add(new SkyboxAsset(1, "shadow", TextureAtlas.Create("shadow", Content.Load<Texture2D>("sprites/skybox/shadow"), 1024, 1024), 1024, 0));
+ Assets.Add(new SkyboxAsset(2, "nebula", TextureAtlas.Create("nebula", Content.Load<Texture2D>("sprites/skybox/nebula"), 1024, 1024), 1024, 0));
+ }
+
+ public SkyboxAsset GetAsset(string name) {
+ return Assets.Find(x => x.Name == name);
+ }
+ }
+
+ public class SkyboxAsset {
+ public readonly byte AssetID;
+ public readonly string Name;
+ public readonly SkyboxPortionFrames Frames;
+
+ public SkyboxAsset(byte id, string name, TextureAtlas atlas, int size, int frameStart, int frameCount) {
+ AssetID = id;
+ Name = name;
+ Frames = new SkyboxPortionFrames(atlas, size, frameStart, frameCount);
+
+ Debug.WriteLine($" Skybox asset '{name}' loaded.");
+ }
+
+ public SkyboxAsset(byte id, string name, TextureAtlas atlas, int size, int frame) : this (id, name, atlas, size, frame, 1) {}
+ }
+} \ No newline at end of file
diff --git a/source/resources/sprites/BlockFrames.cs b/source/resources/sprites/BlockFrames.cs
new file mode 100644
index 0000000..33db3e8
--- /dev/null
+++ b/source/resources/sprites/BlockFrames.cs
@@ -0,0 +1,53 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Sprites {
+ public class BlockFrames {
+ private readonly Vector2 _scaling;
+
+ private BlockFrame[] _frames;
+
+ private bool _doDraw;
+ public bool DoDraw => _doDraw;
+
+ public BlockFrames(TextureAtlas atlas, int startIndex, int frameCount, Vector2? origin = null) {
+ _doDraw = frameCount > 0;
+
+ _scaling = new Vector2(ResourceManager.SPRITE_SCALING);
+
+ _frames = new BlockFrame[frameCount];
+ for (int i = 0; i < frameCount; i++) {
+ _frames[i] = new BlockFrame(atlas.GetRegion(startIndex + i), _scaling, origin);
+ }
+ }
+
+ public BlockFrame GetFrame(int index) {
+ if (!_doDraw) return null;
+
+ return _frames[index % _frames.Length];
+ }
+
+ public BlockFrame GetProgressFrame(float progress) {
+ return GetFrame((int)MathF.Round(progress * (_frames.Length - 1)));
+ }
+ }
+
+ public class BlockFrame {
+ private TextureRegion2D _region;
+ private Vector2 _scaling;
+ private Vector2 _origin;
+
+ public BlockFrame(TextureRegion2D region, Vector2 scaling, Vector2? origin = null) {
+ _region = region;
+ _scaling = scaling;
+ _origin = origin.HasValue ? origin.Value : Vector2.Zero;
+ }
+
+ public void Draw(int index, SpriteBatch spriteBatch, Vector2 position, Color color, float depth = 0f)
+ => spriteBatch.Draw(_region, position, color, 0f, _origin, _scaling, SpriteEffects.None, depth);
+
+ public TextureRegion2D GetRegion() => _region;
+ }
+} \ No newline at end of file
diff --git a/source/resources/sprites/EntityFrames.cs b/source/resources/sprites/EntityFrames.cs
new file mode 100644
index 0000000..65e31ee
--- /dev/null
+++ b/source/resources/sprites/EntityFrames.cs
@@ -0,0 +1,32 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Sprites {
+ public class EntityFrames {
+ public SpriteEffects Effects;
+
+ public int Frame;
+
+ private readonly Vector2 _scaling;
+
+ private TextureRegion2D[] _frames;
+
+ public EntityFrames(TextureAtlas atlas, int startIndex, int frameCount, float scaling) {
+ _scaling = new Vector2(scaling);
+
+ _frames = new TextureRegion2D[frameCount];
+ for (int i = 0; i < frameCount; i++) {
+ _frames[i] = atlas.GetRegion(startIndex + i);
+ }
+ }
+
+ private Vector2 GetOrigin(TextureRegion2D frame) {
+ return new Vector2(0.5f * frame.Width, 0.5f * frame.Height);
+ }
+
+ public void Draw(int index, SpriteBatch spriteBatch, Vector2 position, Vector2 scale, Color color) {
+ spriteBatch.Draw(_frames[Frame % _frames.Length], position, color, 0f, GetOrigin(_frames[index % _frames.Length]), _scaling * scale, Effects, 0f, null);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/sprites/SkyboxPortionFrames.cs b/source/resources/sprites/SkyboxPortionFrames.cs
new file mode 100644
index 0000000..dc26156
--- /dev/null
+++ b/source/resources/sprites/SkyboxPortionFrames.cs
@@ -0,0 +1,51 @@
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Sprites {
+ public class SkyboxPortionFrames {
+ private readonly Vector2 _scaling;
+
+ private TextureAtlas _atlas;
+ private int _start;
+ private int _count;
+
+ private TextureRegion2D[] _frames;
+ private Color _color;
+ private float _depth;
+
+ public SkyboxPortionFrames(TextureAtlas atlas, int size, int startIndex, int frameCount) {
+ _atlas = atlas;
+ _start = startIndex;
+ _count = frameCount;
+ _scaling = new Vector2(ResourceManager.SPRITE_SCALING) / 2f;
+
+ _frames = new TextureRegion2D[frameCount];
+ for (int i = 0; i < frameCount; i++) {
+ _frames[i] = atlas.GetRegion(startIndex + i);
+ }
+ }
+
+ public SkyboxPortionFrames SetColor(Color color) {
+ _color = color;
+ return this;
+ }
+
+ public SkyboxPortionFrames SetDepth(float depth) {
+ _depth = depth;
+ return this;
+ }
+
+ private Vector2 GetOrigin(TextureRegion2D frame) {
+ return new Vector2(0.5f * frame.Width, 0.5f * frame.Height);
+ }
+
+ public void Draw(int index, SpriteBatch spriteBatch, Vector2 position, float rotation, Vector2 scale) {
+ spriteBatch.Draw(_frames[index % _frames.Length], position, _color, rotation, GetOrigin(_frames[index % _frames.Length]), scale * _scaling, SpriteEffects.None, _depth, null);
+ }
+
+ public SkyboxPortionFrames Clone() {
+ return new SkyboxPortionFrames(_atlas, 0, _start, _count).SetColor(_color).SetDepth(_depth);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/BlockType.cs b/source/resources/types/BlockType.cs
new file mode 100644
index 0000000..d55e998
--- /dev/null
+++ b/source/resources/types/BlockType.cs
@@ -0,0 +1,71 @@
+using Celesteia.Graphics.Lighting;
+using Celesteia.Resources.Sprites;
+using MonoGame.Extended;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Types {
+ public class BlockType : IResourceType {
+ private byte id;
+ public readonly string Name;
+ public void SetID(byte value) => id = value;
+ public byte GetID() => id;
+
+ public BlockType(string name) {
+ Name = name;
+ }
+
+ public BlockFrames Frames = null;
+ public NamespacedKey? DropKey = null;
+ public RectangleF? BoundingBox = new RectangleF(0f, 0f, 1f, 1f);
+ public int Strength = 1;
+ public bool Translucent = false;
+ public BlockLightProperties Light = new BlockLightProperties();
+
+
+ public BlockType MakeFrames(TextureAtlas atlas, int frameStart, int frameCount) {
+ Frames = new BlockFrames(atlas, frameStart, frameCount);
+ return this;
+ }
+
+ public BlockType AddDrop(NamespacedKey dropKey) {
+ DropKey = dropKey;
+ return this;
+ }
+
+ public BlockType SetBoundingBox(RectangleF boundingBox) {
+ BoundingBox = boundingBox;
+ return this;
+ }
+
+ public BlockType SetStrength(int strength) {
+ Strength = strength;
+ return this;
+ }
+
+ public BlockType SetLightProperties(BlockLightProperties properties) {
+ Light = properties;
+ if (Light == null) Light = new BlockLightProperties();
+ return this;
+ }
+
+ public BlockType SetTranslucent(bool translucent) {
+ Translucent = translucent;
+ return this;
+ }
+ }
+
+ public class BlockLightProperties {
+ public readonly bool Emits = false;
+ public readonly bool Occludes = true;
+ public readonly int Propagation = 0;
+ public readonly LightColor Color = LightColor.black;
+
+ public BlockLightProperties() {}
+ public BlockLightProperties(LightColor color, int propagation = 0, bool occludes = true) {
+ Emits = !color.Equals(LightColor.black);
+ Propagation = propagation;
+ Occludes = occludes;
+ Color = color;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/EntityType.cs b/source/resources/types/EntityType.cs
new file mode 100644
index 0000000..4ecc20f
--- /dev/null
+++ b/source/resources/types/EntityType.cs
@@ -0,0 +1,20 @@
+using System;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Resources.Types {
+ public class EntityType : IResourceType {
+ private byte id;
+ public byte GetID() => id;
+ public void SetID(byte value) => id = value;
+
+ private Action<Entity> InstantiateAction;
+
+ public EntityType(Action<Entity> instantiate) {
+ InstantiateAction = instantiate;
+ }
+
+ public void Instantiate(Entity entity) {
+ InstantiateAction.Invoke(entity);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/ItemType.cs b/source/resources/types/ItemType.cs
new file mode 100644
index 0000000..8b1da27
--- /dev/null
+++ b/source/resources/types/ItemType.cs
@@ -0,0 +1,21 @@
+using Celesteia.Game.Items;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Types {
+ public class ItemType : IResourceType {
+ private byte id;
+ public readonly string Name;
+ public byte GetID() => id;
+ public void SetID(byte value) => id = value;
+
+ public ItemType(string name) {
+ Name = name;
+ }
+
+ public string Lore = "";
+ public TextureRegion2D Sprite = null;
+ public int MaxStackSize = 99;
+ public IItemActions Actions;
+ public bool ConsumeOnUse;
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/Recipe.cs b/source/resources/types/Recipe.cs
new file mode 100644
index 0000000..94dd9ba
--- /dev/null
+++ b/source/resources/types/Recipe.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using Celesteia.Game.Components.Items;
+
+namespace Celesteia.Resources.Types {
+ public class Recipe : IResourceType {
+ private byte id;
+ public readonly string Name;
+ public byte GetID() => id;
+ public void SetID(byte value) => id = value;
+
+ public Part Result;
+ public List<Part> Ingredients;
+
+ public Recipe(Part result, params Part[] ingredients) {
+ Result = result;
+ Ingredients = new List<Part>(ingredients);
+ }
+
+ public bool Craftable(Inventory inventory) {
+ foreach (Part ingredient in Ingredients)
+ if (!inventory.ContainsAmount(ingredient.Key, ingredient.Amount)) return false;
+
+ return true;
+ }
+
+ public void Craft(Inventory inventory) {
+ if (!Craftable(inventory)) return;
+
+ foreach (Part ingredient in Ingredients)
+ inventory.RemoveAmount(ingredient.Key, ingredient.Amount);
+
+ inventory.AddItem(Result.Stack);
+ }
+ }
+
+ public struct Part {
+ public NamespacedKey Key;
+ public int Amount;
+
+ public Part(NamespacedKey key, int amount) {
+ Key = key;
+ Amount = amount;
+ }
+
+ public ItemStack Stack => new ItemStack(Key, Amount);
+ public ItemType GetItemType() => ResourceManager.Items.GetResource(Key) as ItemType;
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/TileEntityType.cs b/source/resources/types/TileEntityType.cs
new file mode 100644
index 0000000..cd40514
--- /dev/null
+++ b/source/resources/types/TileEntityType.cs
@@ -0,0 +1,27 @@
+using System;
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Entities;
+
+namespace Celesteia.Resources.Types {
+ public class TileEntityType : IResourceType {
+ private byte id;
+ public byte GetID() => id;
+ public void SetID(byte value) => id = value;
+
+ public readonly Point Bounds;
+ public readonly Point Origin;
+ public readonly NamespacedKey PartKey;
+ private Action<Entity> InstantiateAction;
+
+ public TileEntityType(NamespacedKey part, Action<Entity> instantiate, int width, int height, int originX = 0, int originY = 0) {
+ PartKey = part;
+ InstantiateAction = instantiate;
+ Bounds = new Point(width, height);
+ Origin = new Point(originX, originY);
+ }
+
+ public void Instantiate(Entity entity) {
+ InstantiateAction.Invoke(entity);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/builders/BlockTypeBuilder.cs b/source/resources/types/builders/BlockTypeBuilder.cs
new file mode 100644
index 0000000..e08b1a1
--- /dev/null
+++ b/source/resources/types/builders/BlockTypeBuilder.cs
@@ -0,0 +1,82 @@
+using Microsoft.Xna.Framework;
+using MonoGame.Extended;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Types.Builders {
+ public class BlockTypeBuilder {
+ private TextureAtlas _atlas;
+ public BlockTypeBuilder(TextureAtlas atlas) {
+ _atlas = atlas;
+ }
+
+ private BlockType current;
+ public BlockTypeBuilder WithName(string name) {
+ current = new BlockType(name);
+ return this;
+ }
+
+ public BlockTypeBuilder Full() => WithTemplate(BlockTypeTemplate.Full);
+ public BlockTypeBuilder Invisible() => WithTemplate(BlockTypeTemplate.Invisible);
+ public BlockTypeBuilder Walkthrough() => WithTemplate(BlockTypeTemplate.Walkthrough);
+
+ public BlockTypeBuilder WithTemplate(BlockTypeTemplate template) {
+ current.Translucent = template.Translucent;
+ current.BoundingBox = template.BoundingBox;
+ current.DropKey = template.DropKey;
+ current.SetLightProperties(template.LightProperties);
+ current.Strength = template.Strength;
+
+ return this;
+ }
+
+ public BlockTypeBuilder Frames(int start, int count = 1) {
+ current.MakeFrames(_atlas, start, count);
+ return this;
+ }
+
+ public BlockTypeBuilder UniqueFrames(TextureAtlas atlas, int start, int count = 1, Vector2? origin = null) {
+ current.Frames = new Sprites.BlockFrames(atlas, start, count, origin);
+ return this;
+ }
+
+ public BlockTypeBuilder Properties(bool translucent = false, int strength = 1, NamespacedKey? drop = null, BlockLightProperties light = null) {
+ current.Translucent = translucent;
+ current.Strength = strength;
+ current.DropKey = drop;
+ current.SetLightProperties(light);
+
+ return this;
+ }
+
+ public BlockType Get() {
+ return current;
+ }
+ }
+
+ public class BlockTypeTemplate
+ {
+ public static BlockTypeTemplate Invisible = new BlockTypeTemplate(null, null, 0, true);
+ public static BlockTypeTemplate Full = new BlockTypeTemplate(new RectangleF(0f, 0f, 1f, 1f), null, 1, false, null);
+ public static BlockTypeTemplate Walkthrough = new BlockTypeTemplate(null);
+
+ public RectangleF? BoundingBox;
+ public NamespacedKey? DropKey;
+ public int Strength;
+ public bool Translucent;
+ public BlockLightProperties LightProperties;
+
+ public BlockTypeTemplate(
+ RectangleF? boundingBox = null,
+ NamespacedKey? dropKey = null,
+ int strength = 1,
+ bool translucent = false,
+ BlockLightProperties lightProperties = null
+ ) {
+ BoundingBox = boundingBox;
+ DropKey = dropKey;
+ Strength = strength;
+ Translucent = translucent;
+ LightProperties = lightProperties;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/resources/types/builders/ItemTypeBuilder.cs b/source/resources/types/builders/ItemTypeBuilder.cs
new file mode 100644
index 0000000..cd9f61c
--- /dev/null
+++ b/source/resources/types/builders/ItemTypeBuilder.cs
@@ -0,0 +1,67 @@
+using Celesteia.Game.Components;
+using Celesteia.Game.Items;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.Resources.Types.Builders {
+ public class ItemTypeBuilder {
+ private TextureAtlas _atlas;
+ public ItemTypeBuilder(TextureAtlas atlas) {
+ _atlas = atlas;
+ }
+
+ private ItemType current;
+ public ItemTypeBuilder WithName(string name) {
+ current = new ItemType(name);
+ return this;
+ }
+
+ public ItemTypeBuilder Block(NamespacedKey blockToPlace)
+ => Template(ItemTypeTemplate.Block).Actions(new BlockItemActions(blockToPlace));
+ public ItemTypeBuilder Pickaxe(int power)
+ => Template(ItemTypeTemplate.Tool).Actions(new PickaxeItemActions(power));
+ public ItemTypeBuilder Upgrade(EntityAttribute attribute, float increase, float max )
+ => Template(ItemTypeTemplate.Tool).Actions(new UpgradeItemActions(attribute, increase, max));
+
+ public ItemTypeBuilder Template(ItemTypeTemplate template) {
+ current.MaxStackSize = template.MaxStackSize;
+ current.ConsumeOnUse = template.ConsumeOnUse;
+ return this;
+ }
+
+ public ItemTypeBuilder Frame(int frame) {
+ return Frame(_atlas.GetRegion(frame));
+ }
+
+ public ItemTypeBuilder Frame(TextureRegion2D region) {
+ current.Sprite = region;
+ return this;
+ }
+
+ public ItemTypeBuilder Actions(IItemActions actions) {
+ current.Actions = actions;
+ return this;
+ }
+
+ public ItemType Get() {
+ return current;
+ }
+ }
+
+ public class ItemTypeTemplate
+ {
+ public static ItemTypeTemplate Block = new ItemTypeTemplate(1000, true);
+ public static ItemTypeTemplate Tool = new ItemTypeTemplate(1, false);
+ public static ItemTypeTemplate Upgrade = new ItemTypeTemplate(1, false);
+
+ public int MaxStackSize = 99;
+ public bool ConsumeOnUse = true;
+
+ public ItemTypeTemplate(
+ int maxStackSize = 99,
+ bool consumeOnUse = true
+ ) {
+ MaxStackSize = maxStackSize;
+ ConsumeOnUse = consumeOnUse;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/screens/GameplayScreen.cs b/source/screens/GameplayScreen.cs
new file mode 100644
index 0000000..ea1a278
--- /dev/null
+++ b/source/screens/GameplayScreen.cs
@@ -0,0 +1,81 @@
+using System.Diagnostics;
+using Celesteia.Game.Systems;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Celesteia.Game.ECS;
+using MonoGame.Extended.Entities;
+using MonoGame.Extended.Screens;
+using Celesteia.Resources;
+using Celesteia.Graphics;
+using Celesteia.Game.Components;
+using Celesteia.Game.Systems.Physics;
+using Celesteia.GUIs.Game;
+using Celesteia.Game.Systems.UI;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Planets;
+using Microsoft.Xna.Framework.Media;
+
+namespace Celesteia.Screens {
+ public class GameplayScreen : GameScreen {
+ private new GameInstance Game => (GameInstance) base.Game;
+
+ private GameWorld _gameWorld;
+ private ChunkMap _chunkMap;
+ public GameplayScreen(GameInstance game, GameWorld gameWorld) : base(game) {
+ _gameWorld = gameWorld;
+ _chunkMap = gameWorld.ChunkMap;
+ }
+
+ private SpriteBatch SpriteBatch => Game.SpriteBatch;
+ private Camera2D Camera;
+ private GameGUI _gameGui;
+
+ public override void LoadContent()
+ {
+ base.LoadContent();
+
+ Song overworldMusic = Content.Load<Song>("music/landing_light");
+ Game.Music.PlayNow(overworldMusic);
+
+ Camera = new Camera2D(GraphicsDevice);
+
+ _gameGui = new GameGUI(Game);
+ _gameGui.LoadContent(Content);
+
+ LocalPlayerSystem lps = new LocalPlayerSystem(Game, _chunkMap, Camera, SpriteBatch, _gameGui);
+
+ _gameWorld.BeginBuilder()
+ .AddSystem(new PhysicsSystem())
+ .AddSystem(new PhysicsWorldCollisionSystem(_chunkMap))
+ .AddSystem(new TargetPositionSystem(_chunkMap))
+ .AddSystem(new ChunkMapRenderSystem(Camera, SpriteBatch, _chunkMap))
+ .AddSystem(new CameraSystem(Camera))
+ .AddSystem(new CameraRenderSystem(Camera, SpriteBatch))
+ .AddSystem(new LightingSystem(Camera, SpriteBatch, _chunkMap))
+ .AddSystem(lps)
+ .AddSystem(new GameGUIDrawSystem(_gameGui));
+ _gameWorld.EndBuilder();
+
+ Entity player = new EntityFactory(_gameWorld).CreateEntity(NamespacedKey.Base("player"));
+ player.Get<TargetPosition>().Target = _chunkMap.GetSpawnpoint();
+ _gameGui.SetReferenceInventory(player.Get<Inventory>());
+ lps.Player = player;
+ }
+
+ public override void Update(GameTime gameTime) => _gameWorld.Update(gameTime);
+
+ public override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.SkyBlue);
+ _gameWorld.Draw(gameTime);
+ }
+
+ public override void Dispose()
+ {
+ Debug.WriteLine("Unloading GameplayScreen content...");
+ base.UnloadContent();
+ Debug.WriteLine("Disposing GameplayScreen...");
+ base.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/screens/MainMenuScreen.cs b/source/screens/MainMenuScreen.cs
new file mode 100644
index 0000000..0d2a69b
--- /dev/null
+++ b/source/screens/MainMenuScreen.cs
@@ -0,0 +1,85 @@
+using System.Diagnostics;
+using Celesteia.GUIs;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Screens;
+using Microsoft.Xna.Framework.Media;
+using Celesteia.Graphics;
+using MonoGame.Extended.Entities;
+using Celesteia.Game.Systems.MainMenu;
+using Celesteia.Game.ECS;
+using MonoGame.Extended;
+using Celesteia.Game.Components.Skybox;
+using Celesteia.Resources;
+
+namespace Celesteia.Screens {
+ public class MainMenuScreen : GameScreen
+ {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public MainMenuScreen(GameInstance game) : base(game) {}
+
+ private MainMenu mainMenu;
+
+ private Song mainMenuTheme;
+
+ private Camera2D Camera;
+ private World _world;
+
+ public override void LoadContent()
+ {
+ base.LoadContent();
+
+ mainMenuTheme = Content.Load<Song>("music/stargaze_symphony");
+ Game.Music.PlayNow(mainMenuTheme);
+
+ Camera = new Camera2D(GraphicsDevice);
+
+ _world = new WorldBuilder()
+ .AddSystem(new MainMenuBackgroundSystem())
+ .AddSystem(new MainMenuRenderSystem(Camera, Game.SpriteBatch))
+ .Build();
+
+ CreateSkyboxPortion("stars", Color.White, -0.1f, .9f);
+ CreateSkyboxPortion("shadow", Color.Black, 5f, .7f);
+ CreateSkyboxPortion("shadow", Color.Black, 3f, .6f);
+ CreateSkyboxPortion("nebula", new Color(165,216,255,45), 3f, .5f);
+ CreateSkyboxPortion("nebula", new Color(255,165,246,45), -2f, .3f);
+
+ mainMenu = new MainMenu(Game);
+ mainMenu.LoadContent(Content);
+ }
+
+ public Entity CreateSkyboxPortion(string name, Color color, float rotation, float depth)
+ {
+ Entity e = _world.CreateEntity();
+
+ e.Attach(new Transform2(Vector2.Zero, 0F, new Vector2(3f, 3f)));
+ e.Attach(new SkyboxRotateZ(rotation));
+ e.Attach(ResourceManager.Skybox.GetAsset(name).Frames.Clone().SetColor(color).SetDepth(depth));
+
+ return e;
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.White);
+
+ _world.Draw(gameTime);
+ mainMenu.Draw(gameTime);
+ }
+
+ public override void Update(GameTime gameTime)
+ {
+ _world.Update(gameTime);
+ mainMenu.Update(gameTime, out _);
+ }
+
+ public override void Dispose()
+ {
+ Debug.WriteLine("Unloading MainMenuScreen content...");
+ base.UnloadContent();
+ Debug.WriteLine("Disposing MainMenuScreen...");
+ base.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/screens/SplashScreen.cs b/source/screens/SplashScreen.cs
new file mode 100644
index 0000000..46fb519
--- /dev/null
+++ b/source/screens/SplashScreen.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Diagnostics;
+using Celesteia.Game.Input;
+using Celesteia.UI;
+using Celesteia.UI.Elements;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Screens;
+
+namespace Celesteia.Screens {
+ public class SplashScreen : GameScreen {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public SplashScreen(GameInstance game) : base(game) {}
+
+ private Texture2D leafalLogo;
+ private SoundEffect splashSound;
+ private Image logoElement;
+ private Rect logoRect;
+
+ private float logoRatio;
+
+ public override void LoadContent()
+ {
+ base.LoadContent();
+
+ leafalLogo = Game.Content.Load<Texture2D>("branding/leafal/leafal_text_logo");
+ splashSound = Game.Content.Load<SoundEffect>("branding/leafal/splash");
+
+ logoRatio = leafalLogo.Height / (float) leafalLogo.Width;
+
+ logoRect = new Rect(
+ new ScreenSpaceUnit(0.25f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(0.5f - (logoRatio / 2f), ScreenSpaceUnit.ScreenSpaceOrientation.Vertical),
+ new ScreenSpaceUnit(0.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(logoRatio * 0.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal)
+ );
+ logoElement = new Image(logoRect).SetTexture(leafalLogo).SetColor(Color.White);
+
+ splashSound.Play(0.5f, 0f, 0f);
+ }
+
+ private float timeElapsed = 0f;
+ private float fadeInTime = 1.25f;
+ private float fadeOutTime = 0.75f;
+ private float duration = 6f;
+ private float endTimeout = 1f;
+ private float progress = 0f;
+ private Color color = Color.White;
+
+ public override void Update(GameTime gameTime) {
+ if (progress >= 1f || Game.Input.GetAny()) {
+ Game.LoadScreen(new MainMenuScreen(Game), new MonoGame.Extended.Screens.Transitions.FadeTransition(GraphicsDevice, Color.Black));
+ return;
+ }
+
+ timeElapsed += (float) (gameTime.ElapsedGameTime.TotalMilliseconds / 1000f);
+ float alpha = 1f;
+ if (timeElapsed <= fadeInTime) alpha = Math.Min(timeElapsed / fadeInTime, 1f);
+ if (duration - fadeOutTime <= timeElapsed) alpha = Math.Max((duration - timeElapsed) / fadeOutTime, 0f);
+
+ color.A = (byte) ((int) (alpha * 255));
+
+ progress = timeElapsed / (duration + endTimeout);
+ UpdateLogoRect(progress);
+ }
+
+ private float growFactor = 0f;
+ private void UpdateLogoRect(float progress) {
+ Rect r = logoElement.GetRect();
+
+ r.X.SetValue(0.25f - (progress * (growFactor / 2f)));
+ r.Y.SetValue(0.5f - (logoRatio / 2f) - ((logoRatio / 2f) * progress * (growFactor / 2f)));
+ r.Width.SetValue(0.5f + (progress * growFactor));
+ r.Height.SetValue(0.5f * logoRatio + ((progress * growFactor * logoRatio)));
+
+ logoElement.SetRect(r);
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.Black);
+
+ Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearClamp);
+
+ logoElement.SetColor(color);
+ logoElement.Draw(Game.SpriteBatch);
+
+ Game.SpriteBatch.End();
+ }
+
+ public override void Dispose()
+ {
+ Debug.WriteLine("Unloading SplashScreen content...");
+ base.UnloadContent();
+ Debug.WriteLine("Disposing SplashScreen...");
+ base.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/screens/TextTestScreen.cs b/source/screens/TextTestScreen.cs
new file mode 100644
index 0000000..2e772df
--- /dev/null
+++ b/source/screens/TextTestScreen.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Diagnostics;
+using Celesteia.Game.Input;
+using Celesteia.Resources;
+using Celesteia.UI;
+using Celesteia.UI.Elements;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Screens;
+
+namespace Celesteia.Screens {
+ public class TextTestScreen : GameScreen {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public TextTestScreen(GameInstance game) : base(game) {}
+
+ private IContainer Root;
+
+ private Label topLeft;
+ //private Label topCenter;
+ //private Label topRight;
+ //private Label middleLeft;
+ //private Label middle;
+ //private Label middleRight;
+ //private Label bottomLeft;
+ //private Label bottom;
+ private Label bottomRight;
+
+ private TextProperties properties;
+ private float _fontSize = 24f;
+
+ public override void LoadContent()
+ {
+ base.LoadContent();
+
+ Root = new Container(Rect.ScreenFull);
+
+ properties = new TextProperties().SetColor(Color.White).SetFont(ResourceManager.Fonts.GetFontType("Hobo")).SetText("Hello, world!");
+
+ topLeft = new Label(new Rect(AbsoluteUnit.WithValue(50), AbsoluteUnit.WithValue(50), AbsoluteUnit.WithValue(100), AbsoluteUnit.WithValue(100)));
+ Root.AddChild(topLeft);
+ bottomRight = new Label(new Rect(AbsoluteUnit.WithValue(50), AbsoluteUnit.WithValue(150), AbsoluteUnit.WithValue(100), AbsoluteUnit.WithValue(100)));
+ Root.AddChild(bottomRight);
+ }
+
+ public override void Update(GameTime gameTime) {
+ _fontSize += MouseHelper.ScrollDelta;
+ _fontSize = Math.Clamp(_fontSize, 1f, 100f);
+
+ topLeft.SetTextProperties(properties.Clone().SetTextAlignment(TextAlignment.Top | TextAlignment.Left).SetFontSize(_fontSize));
+ bottomRight.SetTextProperties(properties.Clone().SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right).SetFontSize(_fontSize));
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.Black);
+
+ Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp);
+
+ Root.Draw(Game.SpriteBatch);
+
+ Game.SpriteBatch.End();
+ }
+
+ public override void Dispose()
+ {
+ Debug.WriteLine("Unloading TextTestScreen content...");
+ base.UnloadContent();
+ Debug.WriteLine("Disposing TextTestScreen...");
+ base.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/Rect.cs b/source/ui/Rect.cs
new file mode 100644
index 0000000..c26ba8b
--- /dev/null
+++ b/source/ui/Rect.cs
@@ -0,0 +1,188 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.UI {
+ public struct Rect {
+ public static Rect AbsoluteZero = new Rect(AbsoluteUnit.WithValue(0f));
+ public static Rect AbsoluteOne = new Rect(AbsoluteUnit.WithValue(1f));
+ public static Rect ScreenFull = new Rect(
+ new ScreenSpaceUnit(0f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(0f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical),
+ new ScreenSpaceUnit(1f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(1f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical)
+ );
+ public static Rect RelativeFull(Rect parent) => new Rect(
+ new RelativeUnit(0f, parent, RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(0f, parent, RelativeUnit.Orientation.Vertical),
+ new RelativeUnit(1f, parent, RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, parent, RelativeUnit.Orientation.Vertical)
+ );
+
+ public IInterfaceUnit X;
+ public IInterfaceUnit Y;
+ public IInterfaceUnit Width;
+ public IInterfaceUnit Height;
+
+ public Rect(IInterfaceUnit uniform) : this (uniform, uniform, uniform, uniform) { }
+
+ public Rect(IInterfaceUnit x, IInterfaceUnit y, IInterfaceUnit width, IInterfaceUnit height) {
+ this.X = x;
+ this.Y = y;
+ this.Width = width;
+ this.Height = height;
+ }
+
+ public Rect SetX(IInterfaceUnit x) {
+ X = x;
+ return this;
+ }
+
+ public Rect SetY(IInterfaceUnit y) {
+ Y = y;
+ return this;
+ }
+
+ public Rect SetWidth(IInterfaceUnit w) {
+ Width = w;
+ return this;
+ }
+
+ public Rect SetHeight(IInterfaceUnit h) {
+ Height = h;
+ return this;
+ }
+
+ public float[] Resolve() {
+ return new float[] { X.Resolve(), Y.Resolve(), Width.Resolve(), Height.Resolve() };
+ }
+
+ public bool Contains(Point point) {
+ return (
+ point.X >= this.X.Resolve() &&
+ point.Y >= this.Y.Resolve() &&
+ point.X <= this.X.Resolve() + this.Width.Resolve() &&
+ point.Y <= this.Y.Resolve() + this.Height.Resolve()
+ );
+ }
+
+ public bool Contains(int x, int y) {
+ return Contains(new Point(x, y));
+ }
+
+ public override string ToString() {
+ return $"{X.Resolve().ToString("0.00")} {Y.Resolve().ToString("0.00")} {Width.Resolve().ToString("0.00")} {Height.Resolve().ToString("0.00")}";
+ }
+
+ public Rectangle ResolveRectangle()
+ {
+ float[] resolved = this.Resolve();
+ return new Rectangle(
+ (int) MathF.Floor(resolved[0]),
+ (int) MathF.Floor(resolved[1]),
+ (int) MathF.Floor(resolved[2]),
+ (int) MathF.Floor(resolved[3])
+ );
+ }
+ }
+
+ public interface IInterfaceUnit {
+ public float Resolve();
+
+ public void SetValue(float value);
+ }
+
+ public struct AbsoluteUnit : IInterfaceUnit
+ {
+ public float value { get; private set; }
+
+ public AbsoluteUnit(float value) {
+ this.value = value;
+ }
+
+ public static AbsoluteUnit WithValue(float value) {
+ return new AbsoluteUnit(value);
+ }
+
+ public void SetValue(float value) {
+ this.value = value;
+ }
+
+ public float Resolve()
+ {
+ return value;
+ }
+ }
+
+ public struct ScreenSpaceUnit : IInterfaceUnit
+ {
+ public float value { get; private set; }
+ public ScreenSpaceOrientation orientation { get; private set; }
+
+ public ScreenSpaceUnit(float value, ScreenSpaceOrientation orientation) {
+ this.value = value;
+ this.orientation = orientation;
+ }
+
+ public void SetValue(float value) {
+ this.value = value;
+ }
+
+ public void SetOrientation(ScreenSpaceOrientation orientation) {
+ this.orientation = orientation;
+ }
+
+ public float Resolve()
+ {
+ if (UIReferences.gameWindow != null) {
+ switch (orientation) {
+ case ScreenSpaceOrientation.Horizontal:
+ return value * UIReferences.gameWindow.ClientBounds.Width;
+ case ScreenSpaceOrientation.Vertical:
+ return value * UIReferences.gameWindow.ClientBounds.Height;
+ }
+ }
+ return 0f;
+ }
+
+ public enum ScreenSpaceOrientation {
+ Horizontal, Vertical
+ }
+ }
+
+ public struct RelativeUnit : IInterfaceUnit
+ {
+ public float value { get; private set; }
+ public Rect parent { get; private set; }
+ public Orientation orientation { get; private set; }
+
+ public RelativeUnit(float value, Rect parent, Orientation orientation) {
+ this.value = value;
+ this.parent = parent;
+ this.orientation = orientation;
+ }
+
+ public void SetValue(float value) {
+ this.value = value;
+ }
+
+ public void SetOrientation(Orientation orientation) {
+ this.orientation = orientation;
+ }
+
+ public float Resolve()
+ {
+ switch (orientation) {
+ case Orientation.Horizontal:
+ return value * parent.Resolve()[2];
+ case Orientation.Vertical:
+ return value * parent.Resolve()[3];
+ default:
+ return 0f;
+ }
+ }
+
+ public enum Orientation {
+ Horizontal, Vertical
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/TextAlignment.cs b/source/ui/TextAlignment.cs
new file mode 100644
index 0000000..1f92828
--- /dev/null
+++ b/source/ui/TextAlignment.cs
@@ -0,0 +1,10 @@
+using System;
+
+[Flags]
+public enum TextAlignment {
+ Center = 0,
+ Left = 1,
+ Right = 2,
+ Top = 4,
+ Bottom = 8
+} \ No newline at end of file
diff --git a/source/ui/UI.cs b/source/ui/UI.cs
new file mode 100644
index 0000000..39f4136
--- /dev/null
+++ b/source/ui/UI.cs
@@ -0,0 +1,81 @@
+using Celesteia.Resources.Management;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia {
+ public static class UIReferences {
+ public static GameWindow gameWindow;
+ public static bool GUIEnabled = true;
+ public static int Scaling = 3;
+ }
+
+ public static class TextUtilities {
+ public static void DrawAlignedText(SpriteBatch spriteBatch, Rectangle rect, FontType font, string text, Color color, TextAlignment textAlignment, float targetSize) {
+ // Credit for text alignment: https://stackoverflow.com/a/10263903
+
+ // Measure the text's size from the sprite font.
+ Vector2 size = font.Font.MeasureString(text);
+
+ // Get the origin point at the center.
+ Vector2 origin = 0.5f * size;
+
+ if (textAlignment.HasFlag(TextAlignment.Left))
+ origin.X += rect.Width / 2f - size.X / 2f;
+
+ if (textAlignment.HasFlag(TextAlignment.Right))
+ origin.X -= rect.Width / 2f - size.X / 2f;
+
+ if (textAlignment.HasFlag(TextAlignment.Top))
+ origin.Y += rect.Height / 2f - size.Y / 2f;
+
+ if (textAlignment.HasFlag(TextAlignment.Bottom))
+ origin.Y -= rect.Height / 2f - size.Y / 2f;
+
+ spriteBatch.DrawString(font.Font, text, new Vector2(rect.Center.X, rect.Center.Y), color, 0f, origin, font.Scale(targetSize), SpriteEffects.None, 0f);
+ }
+
+ public static void DrawAlignedText(SpriteBatch spriteBatch, Rectangle rect, TextProperties textProperties) {
+ DrawAlignedText(spriteBatch, rect, textProperties.GetFont(), textProperties.GetText(), textProperties.GetColor(), textProperties.GetAlignment(), textProperties.GetFontSize());
+ }
+ }
+
+ public static class ImageUtilities {
+ private static int xLeft(Rectangle rectangle, int patchSize) => rectangle.X;
+ private static int xMiddle(Rectangle rectangle, int patchSize) => rectangle.X + (patchSize * UIReferences.Scaling);
+ private static int xRight(Rectangle rectangle, int patchSize) => rectangle.X + rectangle.Width - (patchSize * UIReferences.Scaling);
+ private static int yTop(Rectangle rectangle, int patchSize) => rectangle.Y;
+ private static int yMiddle(Rectangle rectangle, int patchSize) => rectangle.Y + (patchSize * UIReferences.Scaling);
+ private static int yBottom(Rectangle rectangle, int patchSize) => rectangle.Y + rectangle.Height - (patchSize * UIReferences.Scaling);
+
+ public static void DrawPatched(SpriteBatch spriteBatch, Rectangle rectangle, TextureAtlas patches, int patchSize, Color color) {
+ int y;
+ int scaled = patchSize * UIReferences.Scaling;
+
+ // Top
+ y = yTop(rectangle, patchSize);
+ {
+ spriteBatch.Draw(patches.GetRegion(0), new Rectangle(xLeft(rectangle, patchSize), y, scaled, scaled), color); // Top left
+ spriteBatch.Draw(patches.GetRegion(1), new Rectangle(xMiddle(rectangle, patchSize), y, rectangle.Width - (2 * scaled), scaled), color); // Top center
+ spriteBatch.Draw(patches.GetRegion(2), new Rectangle(xRight(rectangle, patchSize), y, scaled, scaled), color); // Top right
+ }
+
+ // Center
+ y = yMiddle(rectangle, patchSize);
+ {
+ spriteBatch.Draw(patches.GetRegion(3), new Rectangle(xLeft(rectangle, patchSize), y, scaled, rectangle.Height - (2 * scaled)), color); // Left center
+ spriteBatch.Draw(patches.GetRegion(4), new Rectangle(xMiddle(rectangle, patchSize), y, rectangle.Width - (2 * scaled), rectangle.Height - (2 * scaled)), color); // Center
+ spriteBatch.Draw(patches.GetRegion(5), new Rectangle(xRight(rectangle, patchSize), y, scaled, rectangle.Height - (2 * scaled)), color); // Right center
+ }
+
+ // Bottom
+ y = yBottom(rectangle, patchSize);
+ {
+ spriteBatch.Draw(patches.GetRegion(6), new Rectangle(xLeft(rectangle, patchSize), y, scaled, scaled), color); // Bottom left
+ spriteBatch.Draw(patches.GetRegion(7), new Rectangle(xMiddle(rectangle, patchSize), y, rectangle.Width - (2 * scaled), scaled), color); // Bottom center
+ spriteBatch.Draw(patches.GetRegion(8), new Rectangle(xRight(rectangle, patchSize), y, scaled, scaled), color); // Bottom right
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/Button.cs b/source/ui/elements/Button.cs
new file mode 100644
index 0000000..81a6aed
--- /dev/null
+++ b/source/ui/elements/Button.cs
@@ -0,0 +1,173 @@
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Input;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.UI.Elements {
+ public class Button : Clickable {
+ public Button(Rect rect) {
+ SetRect(rect);
+ }
+
+ public Button SetNewRect(Rect rect) {
+ SetRect(rect);
+ return this;
+ }
+
+ public Button SetPivotPoint(Vector2 pivot) {
+ SetPivot(pivot);
+ return this;
+ }
+
+ // TEXT PROPERTIES
+
+ private TextProperties _text;
+
+ public Button SetTextProperties(TextProperties text) {
+ _text = text;
+ return this;
+ }
+
+ public Button SetText(string text) {
+ _text.SetText(text);
+ return this;
+ }
+
+ // COLOR PROPERTIES
+
+ private ButtonColorGroup _colorGroup = new ButtonColorGroup(Color.White);
+ private Color ButtonColor = Color.White;
+ private bool ButtonEnabled = true;
+
+ public Button SetColorGroup(ButtonColorGroup colorGroup) {
+ _colorGroup = colorGroup;
+ return this;
+ }
+
+ public Button SetButtonEnabled(bool enabled) {
+ ButtonEnabled = enabled;
+ return this;
+ }
+
+ // CLICKING PROPERTIES
+
+ private ClickEvent _onMouseDown = null;
+ private ClickEvent _onMouseUp = null;
+
+ public Button SetOnMouseDown(ClickEvent func) {
+ _onMouseDown = func;
+ return this;
+ }
+
+ public Button SetOnMouseUp(ClickEvent func) {
+ _onMouseUp = func;
+ return this;
+ }
+
+ public override void OnMouseDown(MouseButton button, Point position) {
+ base.OnMouseDown(button, position);
+ _onMouseDown?.Invoke(button, position);
+ }
+
+ public override void OnMouseUp(MouseButton button, Point position) {
+ base.OnMouseUp(button, position);
+ _onMouseUp?.Invoke(button, position);
+ }
+
+ // DRAWING PROPERTIES
+
+ private Texture2D _texture;
+ private TextureAtlas _patches;
+ private int _patchSize;
+
+ public Button SetTexture(Texture2D texture) {
+ _texture = texture;
+ return this;
+ }
+
+ public Button MakePatches(int size) {
+ if (_texture != null) {
+ _patchSize = size;
+ _patches = TextureAtlas.Create("buttonPatches", _texture, _patchSize, _patchSize);
+ }
+ return this;
+ }
+
+ // https://gamedev.stackexchange.com/a/118255
+ private float _colorAmount = 0.0f;
+ private bool _prevMouseOver = false;
+ private bool _prevClicked = false;
+ public override void Update(GameTime gameTime, out bool clickedAnything) {
+ clickedAnything = false;
+ if (ButtonColor == GetTargetColor()) return;
+
+ if (_prevMouseOver != GetMouseOver() || _prevClicked != GetClicked()) _colorAmount = 0.0f;
+
+ _colorAmount += (float)gameTime.ElapsedGameTime.TotalSeconds / 0.5f;
+
+ if (_colorAmount > 1.0f)
+ _colorAmount = 0.0f;
+
+ ButtonColor = Color.Lerp(ButtonColor, GetTargetColor(), _colorAmount);
+
+ _prevMouseOver = GetMouseOver();
+ _prevClicked = GetClicked();
+ }
+
+ Rectangle rectangle;
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ rectangle = GetRectangle();
+
+ // Draw the button's texture.
+ if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, rectangle, _patches, _patchSize, ButtonColor);
+ else spriteBatch.Draw(GetTexture(spriteBatch), rectangle, null, ButtonColor);
+
+ TextUtilities.DrawAlignedText(spriteBatch, rectangle, _text);
+ }
+
+ public Texture2D GetTexture(SpriteBatch spriteBatch) {
+ if (_texture == null) {
+ _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1);
+ _texture.SetData(new[] { Color.Gray });
+ }
+
+ return _texture;
+ }
+
+ private Color GetTargetColor() {
+ return ButtonEnabled ? (GetMouseOver() ? (GetClicked() ? _colorGroup.Active : _colorGroup.Hover) : _colorGroup.Normal) : _colorGroup.Disabled;
+ }
+
+ public Button Clone() {
+ return new Button(GetRect())
+ .SetPivotPoint(GetPivot())
+ .SetOnMouseDown(_onMouseDown)
+ .SetOnMouseUp(_onMouseUp)
+ .SetTexture(_texture)
+ .MakePatches(_patchSize)
+ .SetTextProperties(_text)
+ .SetColorGroup(_colorGroup);
+ }
+ }
+
+ public struct ButtonColorGroup {
+ public Color Normal;
+ public Color Disabled;
+ public Color Hover;
+ public Color Active;
+
+ public ButtonColorGroup(Color normal, Color disabled, Color hover, Color active) {
+ Normal = normal;
+ Disabled = disabled;
+ Hover = hover;
+ Active = active;
+ }
+
+ public ButtonColorGroup(Color normal, Color disabled, Color hover) : this (normal, disabled, hover, normal) {}
+
+ public ButtonColorGroup(Color normal, Color disabled) : this (normal, disabled, normal, normal) {}
+ public ButtonColorGroup(Color normal) : this (normal, normal, normal, normal) {}
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/Clickable.cs b/source/ui/elements/Clickable.cs
new file mode 100644
index 0000000..01436a3
--- /dev/null
+++ b/source/ui/elements/Clickable.cs
@@ -0,0 +1,21 @@
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Input;
+
+namespace Celesteia.UI.Elements {
+ public class Clickable : Element, IClickable
+ {
+ private bool _clicked;
+
+ public override void OnMouseOut() {
+ _clicked = false;
+ base.OnMouseOut();
+ }
+ public virtual void OnMouseDown(MouseButton button, Point position) => _clicked = true;
+ public virtual void OnMouseUp(MouseButton button, Point position) => _clicked = false;
+
+ public bool GetClicked() => _clicked;
+ }
+
+ public delegate void HoverEvent();
+ public delegate void ClickEvent(MouseButton button, Point position);
+} \ No newline at end of file
diff --git a/source/ui/elements/Container.cs b/source/ui/elements/Container.cs
new file mode 100644
index 0000000..9080999
--- /dev/null
+++ b/source/ui/elements/Container.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using Celesteia.Game.Input;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Input;
+
+namespace Celesteia.UI.Elements {
+ public class Container : Element, IContainer
+ {
+ private List<IElement> Children;
+
+ public Container(Rect rect) {
+ SetRect(rect);
+ Children = new List<IElement>();
+ }
+
+ public void AddChild(IElement element) {
+ Children.Add(element);
+ element.SetParent(this);
+ }
+
+ public List<IElement> GetChildren() => Children;
+
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ Children.ForEach(element => { if (element.GetEnabled()) element.Draw(spriteBatch); });
+ }
+
+ private Point _mousePosition;
+
+ public override void Update(GameTime gameTime, out bool clickedAnything)
+ {
+ clickedAnything = false;
+ if (!UIReferences.GUIEnabled) return;
+
+ foreach (IElement element in Children) {
+ element.Update(gameTime, out clickedAnything);
+ }
+
+ _mousePosition = MouseHelper.Position;
+
+ if (MouseHelper.Pressed(MouseButton.Left)) clickedAnything = ResolveMouseDown(MouseButton.Left);
+ else if (MouseHelper.Pressed(MouseButton.Right)) clickedAnything = ResolveMouseDown(MouseButton.Right);
+
+ if (MouseHelper.Released(MouseButton.Left)) clickedAnything = ResolveMouseUp(MouseButton.Left);
+ else if (MouseHelper.Released(MouseButton.Right)) clickedAnything = ResolveMouseUp(MouseButton.Right);
+
+ ResolveMouseOver();
+ }
+
+ public bool ResolveMouseDown(MouseButton button) {
+ bool clicked = false;
+ Children.FindAll(x => x is Clickable).ForEach(element => {
+ if (!element.GetEnabled()) return;
+ Clickable clickable = element as Clickable;
+
+ if (clicked = clickable.GetRectangle().Contains(_mousePosition))
+ clickable.OnMouseDown(button, _mousePosition);
+ });
+ return clicked;
+ }
+
+ public bool ResolveMouseUp(MouseButton button) {
+ bool clicked = false;
+ Children.FindAll(x => x is Clickable).ForEach(element => {
+ if (!element.GetEnabled()) return;
+ Clickable clickable = element as Clickable;
+
+ if (clickable.GetRectangle().Contains(_mousePosition)) {
+ clicked = true;
+ clickable.OnMouseUp(button, _mousePosition);
+ }
+ });
+ return clicked;
+ }
+
+ public void ResolveMouseOver() {
+ Children.ForEach(element => {
+ if (!element.GetEnabled()) return;
+ bool over = element.GetRectangle().Contains(_mousePosition);
+ if (over && !element.GetMouseOver()) {
+ element.OnMouseIn();
+ } else if (!over && element.GetMouseOver()) element.OnMouseOut();
+ });
+ }
+
+ public void Dispose() {
+ Children.Clear();
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/Element.cs b/source/ui/elements/Element.cs
new file mode 100644
index 0000000..b95a693
--- /dev/null
+++ b/source/ui/elements/Element.cs
@@ -0,0 +1,53 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements {
+ public class Element : IElement
+ {
+ private bool _enabled = true;
+ private Rect _rect;
+ private bool _isMouseOver;
+ private IContainer _parent;
+ private Vector2 _pivot;
+
+ public virtual bool GetEnabled() => _enabled && (_parent == null || _parent.GetEnabled());
+ public virtual IContainer GetParent() => _parent;
+ public virtual Vector2 GetPivot() => _pivot;
+ public virtual Rect GetRect() => _rect;
+ public virtual void MoveTo(Point point) {
+ _rect.SetX(AbsoluteUnit.WithValue(point.X));
+ _rect.SetY(AbsoluteUnit.WithValue(point.Y));
+ }
+
+ // Get element rectangle with pivot.
+ public virtual Rectangle GetRectangle() {
+ Rectangle r = GetRect().ResolveRectangle();
+
+ if (GetParent() != null) {
+ r.X += GetParent().GetRectangle().X;
+ r.Y += GetParent().GetRectangle().Y;
+ }
+
+ r.X -= (int)MathF.Round(_pivot.X * r.Width);
+ r.Y -= (int)MathF.Round(_pivot.Y * r.Height);
+
+ return r;
+ }
+
+ // Interface setters.
+ public virtual bool SetEnabled(bool value) => _enabled = value;
+ public virtual void SetParent(IContainer container) => _parent = container;
+ public virtual void SetPivot(Vector2 pivot) => _pivot = pivot;
+ public virtual void SetRect(Rect rect) => _rect = rect;
+
+ // Mouse functions
+ public virtual void OnMouseIn() => _isMouseOver = true;
+ public virtual void OnMouseOut() => _isMouseOver = false;
+ public virtual bool GetMouseOver() => _isMouseOver;
+
+ // Engine functions.
+ public virtual void Update(GameTime gameTime, out bool clickedAnything) { clickedAnything = false; }
+ public virtual void Draw(SpriteBatch spriteBatch) { }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/IClickable.cs b/source/ui/elements/IClickable.cs
new file mode 100644
index 0000000..19c2848
--- /dev/null
+++ b/source/ui/elements/IClickable.cs
@@ -0,0 +1,9 @@
+using Microsoft.Xna.Framework;
+using MonoGame.Extended.Input;
+
+namespace Celesteia.UI.Elements {
+ public interface IClickable : IElement {
+ void OnMouseDown(MouseButton button, Point position);
+ void OnMouseUp(MouseButton button, Point position);
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/IContainer.cs b/source/ui/elements/IContainer.cs
new file mode 100644
index 0000000..0d3550c
--- /dev/null
+++ b/source/ui/elements/IContainer.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace Celesteia.UI.Elements {
+ public interface IContainer : IElement, IDisposable {
+ // Get the element's children.
+ List<IElement> GetChildren();
+
+ // Add to the element's children.
+ void AddChild(IElement element);
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/IElement.cs b/source/ui/elements/IElement.cs
new file mode 100644
index 0000000..094d7b7
--- /dev/null
+++ b/source/ui/elements/IElement.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements {
+ public interface IElement {
+ // Is the element enabled?
+ bool GetEnabled();
+
+ // Set whether the element is enabled.
+ bool SetEnabled(bool value);
+
+ // Get the containing rect of the element.
+ Rect GetRect();
+
+ // Set the containing rect of the element.
+ void SetRect(Rect rect);
+
+ // Move to a point.
+ void MoveTo(Point point);
+
+ // Get the rectangle with a pivot point.
+ Rectangle GetRectangle();
+
+ // Gets the pivot point of the element;
+ Vector2 GetPivot();
+
+ // Sets the pivot point of the element;
+ void SetPivot(Vector2 pivot);
+
+ // Called when the mouse position is within the element's containing rect.
+ void OnMouseIn();
+
+ // Called when the mouse position is within the element's containing rect.
+ void OnMouseOut();
+
+ // Get if the element has the mouse over it.
+ bool GetMouseOver();
+
+ // Update the element.
+ void Update(GameTime gameTime, out bool clickedAnything);
+
+ // Draw the element.
+ void Draw(SpriteBatch spriteBatch);
+
+ // Get the element's parent.
+ IContainer GetParent();
+
+ // Set the element's parent.
+ void SetParent(IContainer container);
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/Image.cs b/source/ui/elements/Image.cs
new file mode 100644
index 0000000..de661e8
--- /dev/null
+++ b/source/ui/elements/Image.cs
@@ -0,0 +1,59 @@
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.UI.Elements {
+ public class Image : Element {
+ private Texture2D _texture;
+ public Color _color;
+
+ public Image(Rect rect) {
+ SetRect(rect);
+ }
+
+ public Image SetTexture(Texture2D texture) {
+ _texture = texture;
+ return this;
+ }
+
+ public Image SetColor(Color color) {
+ _color = color;
+ return this;
+ }
+
+ public Image SetPivotPoint(Vector2 pivot) {
+ SetPivot(pivot);
+ return this;
+ }
+
+ private TextureAtlas _patches;
+ private int _patchSize;
+ public Image MakePatches(int size) {
+ if (_texture != null) {
+ _patchSize = size;
+ _patches = TextureAtlas.Create("patches", _texture, _patchSize, _patchSize);
+ }
+ return this;
+ }
+
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, GetRectangle(), _patches, _patchSize, _color);
+ else spriteBatch.Draw(GetTexture(spriteBatch), GetRectangle(), null, _color);
+ }
+
+ public Texture2D GetTexture(SpriteBatch spriteBatch)
+ {
+ if (_texture == null) {
+ // Make a new texture.
+ _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1);
+
+ // Set the default texture to one gray pixel.
+ _texture.SetData(new[] { Color.Gray });
+ }
+
+ return _texture;
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/Label.cs b/source/ui/elements/Label.cs
new file mode 100644
index 0000000..c875791
--- /dev/null
+++ b/source/ui/elements/Label.cs
@@ -0,0 +1,62 @@
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements {
+ public class Label : Element {
+ private Texture2D _background;
+ private TextProperties _text;
+
+ public Label(Rect rect) {
+ SetRect(rect);
+ }
+
+ public Label SetNewRect(Rect rect) {
+ SetRect(rect);
+ return this;
+ }
+
+ public Label SetPivotPoint(Vector2 pivot) {
+ SetPivot(pivot);
+ return this;
+ }
+
+ public Label SetBackground(Texture2D background) {
+ SetTexture(background);
+ return this;
+ }
+
+ public Label SetText(string text) {
+ _text.SetText(text);
+ return this;
+ }
+
+ public Label SetColor(Color color) {
+ _text.SetColor(color);
+ return this;
+ }
+
+ public Label SetTextProperties(TextProperties text) {
+ _text = text;
+ return this;
+ }
+
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ // Draw the label's background, if present.
+ if (_background != null) spriteBatch.Draw(GetTexture(), GetRectangle(), null, Color.White);
+
+ TextUtilities.DrawAlignedText(spriteBatch, GetRectangle(), _text);
+ }
+
+ public Texture2D GetTexture() => _background;
+ public void SetTexture(Texture2D background) => _background = background;
+
+ public Label Clone() {
+ return new Label(GetRect())
+ .SetPivotPoint(GetPivot())
+ .SetBackground(GetTexture())
+ .SetTextProperties(_text);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/CraftingRecipeSlot.cs b/source/ui/elements/game/CraftingRecipeSlot.cs
new file mode 100644
index 0000000..72101d8
--- /dev/null
+++ b/source/ui/elements/game/CraftingRecipeSlot.cs
@@ -0,0 +1,173 @@
+using System;
+using Celesteia.Game.Components.Items;
+using Celesteia.Resources.Types;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Input;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.UI.Elements.Game {
+ public class CraftingRecipeSlot : Clickable {
+ public const float SLOT_SIZE = 64f;
+ public const float SLOT_SPACING = 16f;
+
+ public Inventory referenceInv;
+
+ public CraftingRecipeSlot(Rect rect) {
+ SetRect(rect);
+ }
+
+ public CraftingRecipeSlot SetNewRect(Rect rect) {
+ SetRect(rect);
+ return this;
+ }
+
+ // RECIPE REFERENCE PROPERTIES
+ private Recipe _recipe;
+ public CraftingRecipeSlot SetRecipe(Recipe recipe) {
+ _recipe = recipe;
+ return this;
+ }
+
+ // DRAWING PROPERTIES
+
+ private Texture2D _texture;
+ private TextureAtlas _patches;
+ private int _patchSize;
+
+ public CraftingRecipeSlot SetTexture(Texture2D texture) {
+ _texture = texture;
+ return this;
+ }
+
+ public CraftingRecipeSlot SetPatches(TextureAtlas patches, int size) {
+ if (_texture != null) {
+ _patchSize = size;
+ _patches = patches;
+ }
+ return this;
+ }
+
+ // TEXT PROPERTIES
+
+ private TextProperties _text;
+
+ public CraftingRecipeSlot SetTextProperties(TextProperties text) {
+ _text = text;
+ return this;
+ }
+
+ public CraftingRecipeSlot SetText(string text) {
+ _text.SetText(text);
+ return this;
+ }
+
+ // CLICKING PROPERTIES
+
+ private ClickEvent _onMouseDown = null;
+ private ClickEvent _onMouseUp = null;
+ public delegate void CraftHoverEvent(Recipe recipe);
+ private CraftHoverEvent _onMouseIn = null;
+ private HoverEvent _onMouseOut = null;
+
+ public CraftingRecipeSlot SetOnMouseDown(ClickEvent func) {
+ _onMouseDown = func;
+ return this;
+ }
+
+ public CraftingRecipeSlot SetOnMouseUp(ClickEvent func) {
+ _onMouseUp = func;
+ return this;
+ }
+
+ public CraftingRecipeSlot SetOnMouseIn(CraftHoverEvent func) {
+ _onMouseIn = func;
+ return this;
+ }
+
+ public CraftingRecipeSlot SetOnMouseOut(HoverEvent func) {
+ _onMouseOut = func;
+ return this;
+ }
+
+ public override void OnMouseDown(MouseButton button, Point position) {
+ base.OnMouseDown(button, position);
+ _onMouseDown?.Invoke(button, position);
+ }
+
+ public override void OnMouseUp(MouseButton button, Point position) {
+ base.OnMouseUp(button, position);
+ _onMouseUp?.Invoke(button, position);
+ }
+
+ public override void OnMouseIn() {
+ base.OnMouseIn();
+ if (_recipe != null) _onMouseIn?.Invoke(_recipe);
+ }
+
+ public override void OnMouseOut() {
+ base.OnMouseOut();
+ _onMouseOut?.Invoke();
+ }
+
+ private Rectangle GetScaledTriangle(Rectangle r, float scale) {
+ int newWidth = (int)Math.Round(r.Width * scale);
+ int newHeight = (int)Math.Round(r.Height * scale);
+ return new Rectangle(
+ (int)Math.Round(r.X + ((r.Width - newWidth) / 2f)),
+ (int)Math.Round(r.Y + ((r.Height - newHeight) / 2f)),
+ newWidth,
+ newHeight
+ );
+ }
+
+ Color color;
+ public override void Update(GameTime gameTime, out bool clickedAnything) {
+ base.Update(gameTime, out clickedAnything);
+
+ if (!this.GetEnabled()) return;
+ color = _recipe.Craftable(referenceInv) ? Color.White : Color.Gray;
+ }
+
+ Rectangle rectangle;
+ Rectangle itemRectangle;
+ Rectangle textRectangle;
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ if (_recipe == null) return;
+
+ rectangle = GetRectangle();
+ itemRectangle = GetScaledTriangle(rectangle, .6f);
+ textRectangle = GetScaledTriangle(rectangle, .4f);
+
+ // Draw the slot's texture.
+ if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, rectangle, _patches, _patchSize, color);
+ else spriteBatch.Draw(GetTexture(spriteBatch), rectangle, null, color);
+
+ spriteBatch.Draw(_recipe.Result.GetItemType().Sprite, itemRectangle, color);
+ TextUtilities.DrawAlignedText(spriteBatch, textRectangle, _text.GetFont(), _recipe.Result.Amount + "", _text.GetColor(), _text.GetAlignment(), _text.GetFontSize());
+ }
+
+ public Texture2D GetTexture(SpriteBatch spriteBatch) {
+ if (_texture == null) {
+ _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1);
+ _texture.SetData(new[] { Color.Gray });
+ }
+
+ return _texture;
+ }
+
+ public CraftingRecipeSlot Clone() {
+ return new CraftingRecipeSlot(GetRect())
+ .SetRecipe(_recipe)
+ .SetOnMouseDown(_onMouseDown)
+ .SetOnMouseUp(_onMouseUp)
+ .SetOnMouseIn(_onMouseIn)
+ .SetOnMouseOut(_onMouseOut)
+ .SetTextProperties(_text)
+ .SetTexture(_texture)
+ .SetPatches(_patches, _patchSize);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/CraftingWindow.cs b/source/ui/elements/game/CraftingWindow.cs
new file mode 100644
index 0000000..e5bcc71
--- /dev/null
+++ b/source/ui/elements/game/CraftingWindow.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using Celesteia.Game.Components.Items;
+using Celesteia.GUIs.Game;
+using Celesteia.Resources;
+using Celesteia.Resources.Management;
+using Celesteia.Resources.Types;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements.Game {
+ public class CraftingWindow : Container {
+ private Inventory _referenceInventory;
+ private Image background;
+ private GameGUI _gameGui;
+
+ public CraftingWindow(GameGUI gameGui, Rect rect, Texture2D backgroundImage, Inventory inventory, CraftingRecipeSlot template) : base(rect) {
+ _gameGui = gameGui;
+
+ _referenceInventory = inventory;
+
+ background = new Image(Rect.RelativeFull(rect)).SetTexture(backgroundImage).MakePatches(4).SetColor(Color.White);
+ AddChild(background);
+
+ AddRecipes(27, template);
+ }
+
+ int columns = 9;
+ private void AddRecipes(int amountPerPage, CraftingRecipeSlot template) {
+ int rows = (int)Math.Ceiling(amountPerPage / (double)columns);
+
+ float o = CraftingRecipeSlot.SLOT_SPACING;
+ int index = 0;
+ int i = 0;
+ for (int row = 0; row < rows; row++)
+ for (int column = 0; column < columns; column++) {
+ if (i >= ResourceManager.Recipes.Recipes.Count) break;
+
+ int slotNumber = i;
+ Recipe recipe = ResourceManager.Recipes.Recipes[index];
+ CraftingRecipeSlot slot = template.Clone()
+ .SetNewRect(template.GetRect()
+ .SetX(AbsoluteUnit.WithValue(column * CraftingRecipeSlot.SLOT_SIZE + (column * CraftingRecipeSlot.SLOT_SPACING) + o))
+ .SetY(AbsoluteUnit.WithValue(row * CraftingRecipeSlot.SLOT_SIZE + (row * CraftingRecipeSlot.SLOT_SPACING) + o))
+ )
+ .SetRecipe(recipe)
+ .SetOnMouseUp((button, point) => {
+ if (button == MonoGame.Extended.Input.MouseButton.Left) {
+ recipe.Craft(_referenceInventory);
+ }
+ });
+ slot.referenceInv = _referenceInventory;
+ slot.SetPivot(new Vector2(0f, 0f));
+
+ index++;
+ i++;
+
+ AddChild(slot);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/InventorySlot.cs b/source/ui/elements/game/InventorySlot.cs
new file mode 100644
index 0000000..01663b4
--- /dev/null
+++ b/source/ui/elements/game/InventorySlot.cs
@@ -0,0 +1,191 @@
+using System;
+using Celesteia.Game.Components.Items;
+using Celesteia.Resources.Types;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.Input;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.UI.Elements.Game {
+ public class InventorySlot : Clickable {
+ public const float SLOT_SIZE = 64f;
+ public const float SLOT_SPACING = 16f;
+
+ public InventorySlot(Rect rect) {
+ SetRect(rect);
+ }
+
+ public InventorySlot SetNewRect(Rect rect) {
+ SetRect(rect);
+ return this;
+ }
+
+ // SELECTION
+
+ private bool _selected = false;
+ public InventorySlot SetSelected(bool selected) {
+ _selected = selected;
+ return this;
+ }
+
+ public bool GetSelected() {
+ return _selected;
+ }
+
+
+ // INVENTORY REFERENCE PROPERTIES
+
+ private Inventory _inventory;
+ private int _slot;
+
+ public InventorySlot SetReferenceInventory(Inventory inventory) {
+ _inventory = inventory;
+ return this;
+ }
+
+ public InventorySlot SetSlot(int slot) {
+ _slot = slot;
+ return this;
+ }
+
+ // DRAWING PROPERTIES
+
+ private Texture2D _texture;
+ private TextureAtlas _patches;
+ private int _patchSize;
+
+ public InventorySlot SetTexture(Texture2D texture) {
+ _texture = texture;
+ return this;
+ }
+
+ public InventorySlot SetPatches(TextureAtlas patches, int size) {
+ if (_texture != null) {
+ _patchSize = size;
+ _patches = patches;
+ }
+ return this;
+ }
+
+ // TEXT PROPERTIES
+
+ private TextProperties _text;
+
+ public InventorySlot SetTextProperties(TextProperties text) {
+ _text = text;
+ return this;
+ }
+
+ public InventorySlot SetText(string text) {
+ _text.SetText(text);
+ return this;
+ }
+
+ // CLICKING PROPERTIES
+
+ private ClickEvent _onMouseDown = null;
+ private ClickEvent _onMouseUp = null;
+ public delegate void ItemHoverEvent(ItemType type);
+ private ItemHoverEvent _onMouseIn = null;
+ private HoverEvent _onMouseOut = null;
+
+ public InventorySlot SetOnMouseDown(ClickEvent func) {
+ _onMouseDown = func;
+ return this;
+ }
+
+ public InventorySlot SetOnMouseUp(ClickEvent func) {
+ _onMouseUp = func;
+ return this;
+ }
+
+ public InventorySlot SetOnMouseIn(ItemHoverEvent func) {
+ _onMouseIn = func;
+ return this;
+ }
+
+ public InventorySlot SetOnMouseOut(HoverEvent func) {
+ _onMouseOut = func;
+ return this;
+ }
+
+ public override void OnMouseDown(MouseButton button, Point position) {
+ base.OnMouseDown(button, position);
+ _onMouseDown?.Invoke(button, position);
+ }
+
+ public override void OnMouseUp(MouseButton button, Point position) {
+ base.OnMouseUp(button, position);
+ _onMouseUp?.Invoke(button, position);
+ }
+
+ public override void OnMouseIn() {
+ base.OnMouseIn();
+ if (_inventory.GetSlot(_slot) != null) _onMouseIn?.Invoke(_inventory.GetSlot(_slot).Type);
+ }
+
+ public override void OnMouseOut() {
+ base.OnMouseOut();
+ _onMouseOut?.Invoke();
+ }
+
+ private Rectangle GetScaledTriangle(Rectangle r, float scale) {
+ int newWidth = (int)Math.Round(r.Width * scale);
+ int newHeight = (int)Math.Round(r.Height * scale);
+ return new Rectangle(
+ (int)Math.Round(r.X + ((r.Width - newWidth) / 2f)),
+ (int)Math.Round(r.Y + ((r.Height - newHeight) / 2f)),
+ newWidth,
+ newHeight
+ );
+ }
+
+ Rectangle rectangle;
+ Rectangle itemRectangle;
+ Rectangle textRectangle;
+ ItemStack inSlot;
+ Color slightlyTransparent = new Color(255, 255, 255, 100);
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ if (_inventory == null) return;
+
+ rectangle = GetRectangle();
+ itemRectangle = GetScaledTriangle(rectangle, .6f);
+ textRectangle = GetScaledTriangle(rectangle, .4f);
+
+ // Draw the slot's texture.
+ if (_patches != null) ImageUtilities.DrawPatched(spriteBatch, rectangle, _patches, _patchSize, _selected ? Color.DarkViolet : Color.White);
+ else spriteBatch.Draw(GetTexture(spriteBatch), rectangle, null, Color.White);
+
+ // Draw item if present.
+ inSlot = _inventory.GetSlot(_slot);
+ if (inSlot != null) {
+ spriteBatch.Draw(inSlot.Type.Sprite, itemRectangle, Color.White);
+ if (inSlot.Amount > 1) TextUtilities.DrawAlignedText(spriteBatch, textRectangle, _text.GetFont(), $"{inSlot.Amount}", _text.GetColor(), _text.GetAlignment(), _text.GetFontSize());
+ } else TextUtilities.DrawAlignedText(spriteBatch, rectangle, _text.GetFont(), $"{_slot + 1}", slightlyTransparent, TextAlignment.Center, 24f);
+ }
+
+ public Texture2D GetTexture(SpriteBatch spriteBatch) {
+ if (_texture == null) {
+ _texture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1);
+ _texture.SetData(new[] { Color.Gray });
+ }
+
+ return _texture;
+ }
+
+ public InventorySlot Clone() {
+ return new InventorySlot(GetRect())
+ .SetReferenceInventory(_inventory)
+ .SetOnMouseDown(_onMouseDown)
+ .SetOnMouseUp(_onMouseUp)
+ .SetOnMouseIn(_onMouseIn)
+ .SetOnMouseOut(_onMouseOut)
+ .SetSlot(_slot)
+ .SetTextProperties(_text)
+ .SetTexture(_texture)
+ .SetPatches(_patches, _patchSize);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/InventoryWindow.cs b/source/ui/elements/game/InventoryWindow.cs
new file mode 100644
index 0000000..c8dba41
--- /dev/null
+++ b/source/ui/elements/game/InventoryWindow.cs
@@ -0,0 +1,56 @@
+using System;
+using Celesteia.Game.Components.Items;
+using Celesteia.GUIs.Game;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements.Game {
+ public class InventoryWindow : Container {
+ private Inventory _referenceInventory;
+ private Image background;
+ private GameGUI _gameGui;
+
+ public InventoryWindow(GameGUI gameGui, Rect rect, Texture2D backgroundImage, Inventory inventory, int slots, int offset, InventorySlot template) : base(rect) {
+ _gameGui = gameGui;
+
+ background = new Image(Rect.RelativeFull(rect)).SetTexture(backgroundImage).MakePatches(4).SetColor(Color.White);
+ AddChild(background);
+
+ _referenceInventory = inventory;
+
+ AddSlots(slots, offset, template);
+ }
+
+ int columns = 9;
+ private void AddSlots(int amount, int offset, InventorySlot template) {
+ int rows = (int)Math.Ceiling(amount / (double)columns);
+
+ float o = InventorySlot.SLOT_SPACING;
+ int i = 0;
+ for (int row = 0; row < rows; row++)
+ for (int column = 0; column < 9; column++) {
+ if (i > amount) break;
+
+ int slotNumber = i + offset;
+ InventorySlot slot = template.Clone()
+ .SetNewRect(template.GetRect()
+ .SetX(AbsoluteUnit.WithValue(column * InventorySlot.SLOT_SIZE + (column * InventorySlot.SLOT_SPACING) + o))
+ .SetY(AbsoluteUnit.WithValue(row * InventorySlot.SLOT_SIZE + (row * InventorySlot.SLOT_SPACING) + o))
+ )
+ .SetSlot(slotNumber)
+ .SetOnMouseUp((button, point) => {
+ ItemStack itemInSlot = _referenceInventory.GetSlot(slotNumber);
+ if ((int)_gameGui.State > 0) {
+ _referenceInventory.SetSlot(slotNumber, _gameGui.CursorItem);
+ _gameGui.CursorItem = itemInSlot;
+ }
+ });
+ slot.SetPivot(new Vector2(0f, 0f));
+
+ i++;
+
+ AddChild(slot);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/PauseMenu.cs b/source/ui/elements/game/PauseMenu.cs
new file mode 100644
index 0000000..39281ee
--- /dev/null
+++ b/source/ui/elements/game/PauseMenu.cs
@@ -0,0 +1,74 @@
+using Celesteia.Game.Components.Items;
+using Celesteia.GUIs.Game;
+using Celesteia.Resources;
+using Celesteia.Screens;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.UI.Elements.Game {
+ public class PauseMenu : Container {
+ private Image background;
+ private IContainer centerMenu;
+ private GameGUI _gameGui;
+
+ private float buttonRow(int number) => number * (buttonHeight + buttonSpacing);
+ private float buttonHeight = 56f;
+ private float buttonSpacing = 10f;
+
+ public PauseMenu(GameGUI gameGui, Rect rect, Button buttonTemplate) : base(rect) {
+ _gameGui = gameGui;
+
+ background = new Image(Rect.RelativeFull(rect)).SetColor(new Color(0, 0, 0, 100));
+ AddChild(background);
+
+ centerMenu = new Container(new Rect(
+ new RelativeUnit(0.5f, GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(0.5f, GetRect(), RelativeUnit.Orientation.Vertical),
+ AbsoluteUnit.WithValue(350f),
+ AbsoluteUnit.WithValue(2 * (buttonHeight + buttonSpacing) - buttonSpacing)
+ ));
+ AddChild(centerMenu);
+
+ AddButtons(buttonTemplate);
+ }
+
+ private void AddButtons(Button template) {
+ centerMenu.AddChild(new Label(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(buttonRow(-1)),
+ new RelativeUnit(1f, centerMenu.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(buttonHeight)
+ ))
+ .SetPivotPoint(new Vector2(0.5f, 0.5f))
+ .SetTextProperties(new TextProperties().SetColor(Color.White).SetFont(ResourceManager.Fonts.GetFontType("Hobo")).SetFontSize(24f).SetTextAlignment(TextAlignment.Center))
+ .SetText("Paused")
+ );
+
+ centerMenu.AddChild(template.Clone()
+ .SetNewRect(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(buttonRow(0)),
+ new RelativeUnit(1f, centerMenu.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(buttonHeight)
+ ))
+ .SetText("Back to Game")
+ .SetOnMouseUp((button, point) => {
+ _gameGui.TogglePause();
+ })
+ );
+
+ centerMenu.AddChild(template.Clone()
+ .SetNewRect(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(buttonRow(1)),
+ new RelativeUnit(1f, centerMenu.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(buttonHeight)
+ ))
+ .SetText("Return to Title")
+ .SetOnMouseUp((button, point) => {
+ _gameGui.Game.LoadScreen(new MainMenuScreen(_gameGui.Game),new MonoGame.Extended.Screens.Transitions.FadeTransition(_gameGui.Game.GraphicsDevice, Color.Black));
+ })
+ );
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/controls/ControlTips.cs b/source/ui/elements/game/controls/ControlTips.cs
new file mode 100644
index 0000000..904b3cc
--- /dev/null
+++ b/source/ui/elements/game/controls/ControlTips.cs
@@ -0,0 +1,51 @@
+using System.Collections.Generic;
+using Celesteia.Game.Input;
+using Celesteia.Resources;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace Celesteia.UI.Elements.Game.Controls {
+ public class ControlTips : Container {
+ private TextProperties _properties;
+ private Dictionary<Keys, string> _keyboardControls = new Dictionary<Keys, string>();
+ private List<string> _lines = new List<string>();
+ private List<Label> _labels = new List<Label>();
+
+ public ControlTips(Rect rect) : base(rect) {
+ _properties = new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.DEFAULT)
+ .SetFontSize(12f)
+ .SetTextAlignment(TextAlignment.Left);
+ }
+
+ private int lineHeight = 16;
+ private Rect LineRect(int line) => new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(line * lineHeight),
+ new RelativeUnit(1f, GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(lineHeight)
+ );
+
+ private void UpdateLines() {
+ _labels.Clear();
+
+ foreach (Keys keys in _keyboardControls.Keys) _lines.Add($"[{keys}] {_keyboardControls[keys]}");
+
+ for (int i = 0; i < _lines.Count; i++) {
+ Label label = new Label(LineRect(i - (_lines.Count / 2)))
+ .SetTextProperties(_properties)
+ .SetText(_lines[i]);
+ label.SetParent(this);
+ _labels.Add(label);
+ }
+ }
+
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ foreach (Label l in _labels) l.Draw(spriteBatch);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs b/source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs
new file mode 100644
index 0000000..a7c6e2f
--- /dev/null
+++ b/source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs
@@ -0,0 +1,97 @@
+using Celesteia.Resources.Types;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements.Game.Tooltips {
+ public class CraftingTooltipDisplay : TooltipDisplay
+ {
+ private const float OUTER_SPACING = 16f;
+ private const float INNER_SPACING = 8f;
+ public readonly Container Content;
+ public readonly Label Title;
+ public readonly ItemDisplay Item;
+ public Container Recipe;
+
+ public CraftingTooltipDisplay(Rect rect, Texture2D background) : base(rect) {
+ AddChild(new Image(Rect.RelativeFull(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(256f + (2 * OUTER_SPACING)),
+ AbsoluteUnit.WithValue(64f + (1 * INNER_SPACING) + (2 * OUTER_SPACING))
+ ))).SetTexture(background).MakePatches(4).SetColor(Color.White));
+
+ Content = new Container(new Rect(
+ AbsoluteUnit.WithValue(OUTER_SPACING),
+ AbsoluteUnit.WithValue(OUTER_SPACING),
+ AbsoluteUnit.WithValue(256f),
+ AbsoluteUnit.WithValue(64f + (1 * INNER_SPACING))
+ ));
+
+ Container titleCard = new Container(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(32f)
+ ));
+ titleCard.AddChild(Item = new ItemDisplay(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(32f),
+ AbsoluteUnit.WithValue(32f)
+ )) {
+ Text = new TextProperties().Standard().SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right)
+ });
+ titleCard.AddChild(Title = new Label(new Rect(
+ AbsoluteUnit.WithValue(72f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(150f),
+ AbsoluteUnit.WithValue(32f)
+ )).SetTextProperties(new Properties.TextProperties().Standard().SetTextAlignment(TextAlignment.Left)).SetPivotPoint(new Vector2(0f, 0f)));
+ Content.AddChild(titleCard);
+
+ Recipe = new Container(new Rect(
+ new RelativeUnit(.5f, Content.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(32f + INNER_SPACING),
+ new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(32f)
+ ));
+ Content.AddChild(Recipe);
+
+ AddChild(Content);
+
+ SetEnabled(false);
+ }
+
+ public void SetRecipe(Recipe recipe) {
+ Item.Item = recipe.Result.GetItemType();
+ Title.SetText(recipe.Result.GetItemType().Name);
+
+ if (Recipe != null) Recipe.Dispose();
+ Recipe = new Container(new Rect(
+ new RelativeUnit(0f, Content.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(32f + INNER_SPACING),
+ new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(32f)
+ ));
+ Recipe.SetPivot(new Vector2(0f, 0f));
+
+ for (int i = 0; i < recipe.Ingredients.Count; i++)
+ Recipe.AddChild(new ItemDisplay(new Rect(
+ AbsoluteUnit.WithValue((i * INNER_SPACING) + (i * 32)),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(32f),
+ AbsoluteUnit.WithValue(32f)
+ )) {
+ Item = recipe.Ingredients[i].GetItemType(),
+ Amount = recipe.Ingredients[i].Amount,
+ Text = new TextProperties().Standard()
+ .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right)
+ .SetFontSize(12f)
+ .SetText(recipe.Ingredients[i].Amount.ToString())
+ });
+
+ Content.AddChild(Recipe);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/tooltips/ItemDisplay.cs b/source/ui/elements/game/tooltips/ItemDisplay.cs
new file mode 100644
index 0000000..d3b1524
--- /dev/null
+++ b/source/ui/elements/game/tooltips/ItemDisplay.cs
@@ -0,0 +1,21 @@
+using Celesteia.Resources.Types;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.UI.Elements.Game.Tooltips {
+ public class ItemDisplay : Element {
+ public ItemType Item;
+ public int Amount;
+ public TextProperties Text;
+
+ public ItemDisplay(Rect rect) => SetRect(rect);
+
+ public override void Draw(SpriteBatch spriteBatch)
+ {
+ spriteBatch.Draw(Item.Sprite, GetRectangle(), Color.White, null);
+ if (Amount > 1) TextUtilities.DrawAlignedText(spriteBatch, GetRectangle(), Text);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/tooltips/ItemTooltipDisplay.cs b/source/ui/elements/game/tooltips/ItemTooltipDisplay.cs
new file mode 100644
index 0000000..9b62af4
--- /dev/null
+++ b/source/ui/elements/game/tooltips/ItemTooltipDisplay.cs
@@ -0,0 +1,62 @@
+using Celesteia.Resources.Types;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.UI.Elements.Game.Tooltips {
+ public class ItemTooltipDisplay : TooltipDisplay
+ {
+ private const float OUTER_SPACING = 16f;
+ private const float INNER_SPACING = 8f;
+ public readonly Container Content;
+ public readonly Label Title;
+ public readonly ItemDisplay Item;
+
+ public ItemTooltipDisplay(Rect rect, Texture2D background) : base(rect) {
+ AddChild(new Image(Rect.RelativeFull(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(256f + (2 * OUTER_SPACING)),
+ AbsoluteUnit.WithValue(32f + (2 * OUTER_SPACING))
+ ))).SetTexture(background).MakePatches(4).SetColor(Color.White));
+
+ Content = new Container(new Rect(
+ AbsoluteUnit.WithValue(OUTER_SPACING),
+ AbsoluteUnit.WithValue(OUTER_SPACING),
+ AbsoluteUnit.WithValue(256f),
+ AbsoluteUnit.WithValue(32f)
+ ));
+
+ Container titleCard = new Container(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ new RelativeUnit(1f, Content.GetRect(), RelativeUnit.Orientation.Horizontal),
+ AbsoluteUnit.WithValue(32f)
+ ));
+ titleCard.AddChild(Item = new ItemDisplay(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(32f),
+ AbsoluteUnit.WithValue(32f)
+ )) {
+ Text = new TextProperties().Standard().SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right)
+ });
+ titleCard.AddChild(Title = new Label(new Rect(
+ AbsoluteUnit.WithValue(72f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(150f),
+ AbsoluteUnit.WithValue(32f)
+ )).SetTextProperties(new Properties.TextProperties().Standard().SetTextAlignment(TextAlignment.Left)).SetPivotPoint(new Vector2(0f, 0f)));
+ Content.AddChild(titleCard);
+
+ AddChild(Content);
+
+ SetEnabled(false);
+ }
+
+ public void SetItem(ItemType type) {
+ Item.Item = type;
+ Title.SetText(type.Name);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/elements/game/tooltips/TooltipDisplay.cs b/source/ui/elements/game/tooltips/TooltipDisplay.cs
new file mode 100644
index 0000000..2ff051d
--- /dev/null
+++ b/source/ui/elements/game/tooltips/TooltipDisplay.cs
@@ -0,0 +1,8 @@
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.UI.Elements.Game.Tooltips {
+ public class TooltipDisplay : Container
+ {
+ public TooltipDisplay(Rect rect) : base(rect) {}
+ }
+} \ No newline at end of file
diff --git a/source/ui/guis/DebugGUI.cs b/source/ui/guis/DebugGUI.cs
new file mode 100644
index 0000000..7ee44ae
--- /dev/null
+++ b/source/ui/guis/DebugGUI.cs
@@ -0,0 +1,66 @@
+using System.Diagnostics;
+using Celesteia.Resources;
+using Celesteia.UI;
+using Celesteia.UI.Elements;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using MonoGame.Extended;
+
+namespace Celesteia.GUIs {
+ public class DebugGUI : GUI {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public DebugGUI(GameInstance game) : base(game, Rect.ScreenFull) {}
+
+ private Label updateLabel;
+ private Label drawLabel;
+
+ public override void LoadContent(ContentManager Content) {
+ float fontSize = 12f;
+
+ Label template = new Label(new Rect(
+ AbsoluteUnit.WithValue(10),
+ AbsoluteUnit.WithValue(10),
+ AbsoluteUnit.WithValue(200),
+ AbsoluteUnit.WithValue(50)
+ ))
+ .SetTextProperties(new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.GetFontType("Hobo"))
+ .SetFontSize(fontSize)
+ .SetTextAlignment(TextAlignment.Top | TextAlignment.Left)
+ );
+ float textSpacing = 4f;
+ float textRow(int number) => 10f + number * (fontSize + textSpacing);
+
+ Root.AddChild(template.Clone().SetNewRect(template.GetRect().SetY(AbsoluteUnit.WithValue(textRow(0)))).SetText($"Celesteia {GameInstance.Version}"));
+ Root.AddChild(updateLabel = template.Clone().SetNewRect(template.GetRect().SetY(AbsoluteUnit.WithValue(textRow(1)))).SetText(""));
+ Root.AddChild(drawLabel = template.Clone().SetNewRect(template.GetRect().SetY(AbsoluteUnit.WithValue(textRow(2)))).SetText(""));
+
+ Debug.WriteLine("Loaded Debug GUI.");
+ }
+
+ private double updateTime;
+ private double lastUpdate;
+ public override void Update(GameTime gameTime, out bool clickedAnything) {
+ clickedAnything = false;
+
+ updateTime = gameTime.TotalGameTime.TotalMilliseconds - lastUpdate;
+ lastUpdate = gameTime.TotalGameTime.TotalMilliseconds;
+
+ updateLabel.SetText($"Update: {updateTime.ToString("0.00")}ms");
+ }
+
+ private double drawTime;
+ private double lastDraw;
+ public override void Draw(GameTime gameTime)
+ {
+ drawTime = gameTime.TotalGameTime.TotalMilliseconds - lastDraw;
+ lastDraw = gameTime.TotalGameTime.TotalMilliseconds;
+
+ drawLabel.SetText($"Draw: {drawTime.ToString("0.00")}ms");
+
+ if (GameInstance.DebugMode) base.Draw(gameTime);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/guis/GUI.cs b/source/ui/guis/GUI.cs
new file mode 100644
index 0000000..6d00070
--- /dev/null
+++ b/source/ui/guis/GUI.cs
@@ -0,0 +1,48 @@
+using System.Diagnostics;
+using Celesteia.UI;
+using Celesteia.UI.Elements;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.GUIs {
+ public class GUI {
+ public GameInstance Game;
+
+ public IContainer Root;
+
+ public GUI(GameInstance Game, Rect rect) {
+ this.Game = Game;
+ this.Root = new Container(rect);
+ }
+
+ public virtual void LoadContent(ContentManager Content) {
+ Debug.WriteLine("Loaded GUI.");
+ }
+
+ public virtual void Update(GameTime gameTime, out bool clickedAnything) {
+ if (!Game.IsActive) {
+ clickedAnything = false;
+ return;
+ }
+
+ Root.Update(gameTime, out clickedAnything);
+ }
+
+ // Draw all elements.
+ public virtual void Draw(GameTime gameTime) {
+
+ Game.SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointWrap, null, null, null);
+
+ if (UIReferences.GUIEnabled) Root.Draw(Game.SpriteBatch);
+
+ Game.SpriteBatch.End();
+ }
+
+ // If the menu is referred to as a boolean, return whether it is non-null (true) or null (false).
+ public static implicit operator bool(GUI gui)
+ {
+ return !object.ReferenceEquals(gui, null);
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/guis/MainMenu.cs b/source/ui/guis/MainMenu.cs
new file mode 100644
index 0000000..dd03d5a
--- /dev/null
+++ b/source/ui/guis/MainMenu.cs
@@ -0,0 +1,225 @@
+using System.Diagnostics;
+using Celesteia.Game.ECS;
+using Celesteia.Resources;
+using Celesteia.Screens;
+using Celesteia.UI;
+using Celesteia.UI.Elements;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Celesteia.GUIs {
+ public class MainMenu : GUI {
+ public MainMenu(GameInstance game) : base(game, Rect.ScreenFull) {}
+
+ private Texture2D logo;
+ private Texture2D buttonTexture;
+
+ private IContainer MainScreen;
+ private IContainer OptionsScreen;
+ private IContainer NewWorldScreen;
+
+ private IContainer LogoPivot;
+ private Label Progress;
+
+ private Button buttonTemplate;
+ private float buttonRow(int number) => number * (buttonHeight + buttonSpacing);
+ private float buttonHeight = 56f;
+ private float buttonSpacing = 10f;
+
+ public override void LoadContent(ContentManager Content) {
+ logo = Game.Content.Load<Texture2D>("celesteia/logo");
+ buttonTexture = Game.Content.Load<Texture2D>("sprites/ui/button");
+
+ buttonTemplate = new Button(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(buttonRow(0)),
+ AbsoluteUnit.WithValue(250f),
+ AbsoluteUnit.WithValue(56f)
+ ))
+ .SetPivotPoint(new Vector2(.5f))
+ .SetTexture(buttonTexture)
+ .MakePatches(4)
+ .SetTextProperties(new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.GetFontType("Hobo"))
+ .SetFontSize(24f)
+ .SetTextAlignment(TextAlignment.Center))
+ .SetColorGroup(new ButtonColorGroup(Color.White, Color.Black, Color.Violet, Color.DarkViolet));
+
+ // Load all the screens.
+ LoadMainScreen();
+ LoadOptionsScreen();
+ LoadNewWorldScreen();
+ LoadGlobals();
+
+ base.LoadContent(Content);
+ }
+
+ private void LoadMainScreen() {
+ Root.AddChild(MainScreen = new Container(Rect.ScreenFull));
+ MainScreen.SetEnabled(true);
+
+ IContainer menu = new Container(new Rect(
+ new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical),
+ AbsoluteUnit.WithValue(250f),
+ AbsoluteUnit.WithValue(0f)
+ ));
+
+ menu.AddChild(LogoPivot = new Container(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ new ScreenSpaceUnit(-.25f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f)
+ )));
+
+ float logoRatio = logo.Height / (float) logo.Width;
+ LogoPivot.AddChild(
+ new Image(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ new ScreenSpaceUnit(.4f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(.4f * logoRatio, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal)
+ ))
+ .SetTexture(logo)
+ .SetColor(Color.White)
+ .SetPivotPoint(new Vector2(.5f))
+ );
+
+ menu.AddChild(
+ buttonTemplate.Clone()
+ .SetNewRect(buttonTemplate.GetRect()
+ .SetY(AbsoluteUnit.WithValue(buttonRow(0)))
+ .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal))
+ )
+ .SetOnMouseUp(async (button, position) => {
+ ShowNewWorldScreen();
+ Debug.WriteLine("Generating world...");
+ GameWorld _gameWorld = await Game.Worlds.LoadNewWorld((progressReport) => {
+ Progress.SetText(progressReport);
+ Debug.WriteLine(" " + progressReport);
+ });
+ Game.LoadScreen(
+ new GameplayScreen(Game, _gameWorld),
+ new MonoGame.Extended.Screens.Transitions.FadeTransition(Game.GraphicsDevice, Color.Black)
+ );
+ })
+ .SetText("Start Game")
+ );
+
+ menu.AddChild(
+ buttonTemplate.Clone()
+ .SetNewRect(buttonTemplate.GetRect()
+ .SetY(AbsoluteUnit.WithValue(buttonRow(1)))
+ .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal))
+ )
+ .SetOnMouseUp((button, position) => { ShowOptionsScreen(); })
+ .SetText("Options")
+ );
+
+ menu.AddChild(
+ buttonTemplate.Clone()
+ .SetNewRect(buttonTemplate.GetRect()
+ .SetY(AbsoluteUnit.WithValue(buttonRow(2)))
+ .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal))
+ )
+ .SetOnMouseUp((button, position) => { Game.Exit(); })
+ .SetText("Quit Game")
+ .SetColorGroup(new ButtonColorGroup(Color.White, Color.Black, Color.Red, Color.DarkRed))
+ );
+
+ MainScreen.AddChild(menu);
+ }
+ private void ShowMainScreen() {
+ MainScreen.SetEnabled(true);
+ OptionsScreen.SetEnabled(false);
+ NewWorldScreen.SetEnabled(false);
+ }
+
+ private void LoadOptionsScreen() {
+ Root.AddChild(OptionsScreen = new Container(Rect.ScreenFull));
+ OptionsScreen.SetEnabled(false);
+
+ IContainer menu = new Container(new Rect(
+ new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical),
+ AbsoluteUnit.WithValue(450f),
+ AbsoluteUnit.WithValue(0f)
+ ));
+
+ menu.AddChild(
+ buttonTemplate.Clone()
+ .SetNewRect(buttonTemplate.GetRect()
+ .SetY(AbsoluteUnit.WithValue(buttonRow(0)))
+ .SetWidth(new RelativeUnit(1f, menu.GetRect(), RelativeUnit.Orientation.Horizontal))
+ )
+ .SetOnMouseUp((button, position) => { ShowMainScreen(); })
+ .SetText("Back to Main Menu")
+ );
+
+ OptionsScreen.AddChild(menu);
+ }
+ private void ShowOptionsScreen() {
+ MainScreen.SetEnabled(false);
+ OptionsScreen.SetEnabled(true);
+ NewWorldScreen.SetEnabled(false);
+ }
+
+ private void LoadNewWorldScreen() {
+ Root.AddChild(NewWorldScreen = new Container(Rect.ScreenFull));
+ NewWorldScreen.SetEnabled(false);
+
+ NewWorldScreen.AddChild(Progress = new Label(
+ new Rect(
+ new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Horizontal),
+ new ScreenSpaceUnit(.5f, ScreenSpaceUnit.ScreenSpaceOrientation.Vertical),
+ AbsoluteUnit.WithValue(200),
+ AbsoluteUnit.WithValue(50)
+ ))
+ .SetPivotPoint(new Vector2(0.5f, 0.5f))
+ .SetTextProperties(new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.DEFAULT)
+ .SetFontSize(24f)
+ .SetTextAlignment(TextAlignment.Center)
+ )
+ .SetText("")
+ );
+ }
+ private void ShowNewWorldScreen() {
+ MainScreen.SetEnabled(false);
+ OptionsScreen.SetEnabled(false);
+ NewWorldScreen.SetEnabled(true);
+ }
+
+ private void LoadGlobals() {
+ Container copyPivot = new Container(new Rect(
+ new RelativeUnit(.5f, Root.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, Root.GetRect(), RelativeUnit.Orientation.Vertical),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(12f)
+ ));
+ copyPivot.SetPivot(new Vector2(.5f, 1f));
+ Root.AddChild(copyPivot);
+
+ copyPivot.AddChild(new Label(
+ new Rect(
+ AbsoluteUnit.WithValue(12f),
+ AbsoluteUnit.WithValue(0f),
+ new RelativeUnit(1f, Root.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, copyPivot.GetRect(), RelativeUnit.Orientation.Vertical)
+ )
+ )
+ .SetTextProperties(new TextProperties()
+ .SetFont(ResourceManager.Fonts.DEFAULT)
+ .SetColor(Color.White)
+ .SetFontSize(12f)
+ .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Center)
+ )
+ .SetText("(c) leafal.io 2022-2023, all rights reserved.")
+ .SetPivotPoint(new Vector2(0.5f, 1f)));
+ }
+ }
+} \ No newline at end of file
diff --git a/source/ui/guis/game/GameGUI.cs b/source/ui/guis/game/GameGUI.cs
new file mode 100644
index 0000000..b8e6f4a
--- /dev/null
+++ b/source/ui/guis/game/GameGUI.cs
@@ -0,0 +1,349 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using Celesteia.Game.Components.Items;
+using Celesteia.Game.Input;
+using Celesteia.Resources;
+using Celesteia.UI;
+using Celesteia.UI.Elements;
+using Celesteia.UI.Elements.Game;
+using Celesteia.UI.Elements.Game.Controls;
+using Celesteia.UI.Elements.Game.Tooltips;
+using Celesteia.UI.Properties;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
+using MonoGame.Extended.TextureAtlases;
+
+namespace Celesteia.GUIs.Game {
+ public class GameGUI : GUI
+ {
+ private new GameInstance Game => (GameInstance) base.Game;
+ public GameGUI(GameInstance Game) : base(Game, Rect.ScreenFull) { }
+
+
+ public ItemStack CursorItem;
+
+ private IContainer _pauseMenu;
+
+ public ControlTips Controls { get; private set; }
+
+ private IContainer ItemManagement;
+
+ private InventorySlot _slotTemplate;
+ private CraftingRecipeSlot _recipeTemplate;
+ private IContainer Hotbar;
+
+
+ private Texture2D slotTexture;
+ private TextureAtlas slotPatches;
+ private Texture2D windowTexture;
+ private Texture2D tooltipTexture;
+
+ private Inventory _inventory;
+ private List<InventorySlot> _slots;
+
+ private int _hotbarSelection = 0;
+ public int HotbarSelection {
+ get => _hotbarSelection;
+ set {
+ _hotbarSelection = MathHelper.Clamp(value, 0, HotbarSlots - 1);
+ UpdateSelected();
+ }
+ }
+ public readonly int HotbarSlots = 9;
+
+ private IContainer _mousePivot;
+ private IContainer _inventoryScreen;
+ private IContainer _craftingScreen;
+
+ private InventoryScreenState _state;
+ public InventoryScreenState State {
+ get => _state;
+ set {
+ _state = value;
+ _inventoryScreen.SetEnabled((int)_state > 0);
+ _craftingScreen.SetEnabled((int)_state > 1);
+
+ if ((int)_state < 1 && CursorItem != null) {
+ ItemStack item = CursorItem;
+ CursorItem = null;
+
+ _inventory.AddItem(item);
+ }
+ }
+ }
+
+ private ItemTooltipDisplay _itemDisplay;
+ private bool _itemDisplayEnabled = false;
+ private CraftingTooltipDisplay _craftingDisplay;
+ private bool _craftingDisplayEnabled = false;
+
+ public void SetReferenceInventory(Inventory inventory) {
+ _inventory = inventory;
+
+ _slotTemplate.SetReferenceInventory(_inventory);
+
+ LoadHotbar();
+ LoadInventoryScreen();
+ LoadCraftingScreen();
+
+ State = _state;
+ }
+
+ public override void LoadContent(ContentManager Content)
+ {
+ Root.AddChild(Controls = new ControlTips(new Rect(
+ AbsoluteUnit.WithValue(8f),
+ new RelativeUnit(0.5f, Root.GetRect(), RelativeUnit.Orientation.Vertical),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f)
+ )));
+
+ slotTexture = Content.Load<Texture2D>("sprites/ui/button");
+ windowTexture = Content.Load<Texture2D>("sprites/ui/button");
+ tooltipTexture = Content.Load<Texture2D>("sprites/ui/window");
+ slotPatches = TextureAtlas.Create("patches", slotTexture, 4, 4);
+
+ LoadTooltipDisplays(Content);
+ LoadPauseMenu(Content);
+
+ _slotTemplate = new InventorySlot(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(-InventorySlot.SLOT_SPACING),
+ AbsoluteUnit.WithValue(InventorySlot.SLOT_SIZE),
+ AbsoluteUnit.WithValue(InventorySlot.SLOT_SIZE)
+ ))
+ .SetTexture(slotTexture)
+ .SetPatches(slotPatches, 4)
+ .SetTextProperties(new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.GetFontType("Hobo"))
+ .SetFontSize(16f)
+ .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Center)
+ )
+ .SetOnMouseIn((item) => {
+ if ((int)State < 1) return;
+ _itemDisplay.SetItem(item);
+ _itemDisplayEnabled = true;
+ })
+ .SetOnMouseOut(() => _itemDisplayEnabled = false);
+
+ _recipeTemplate = new CraftingRecipeSlot(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(-CraftingRecipeSlot.SLOT_SPACING),
+ AbsoluteUnit.WithValue(CraftingRecipeSlot.SLOT_SIZE),
+ AbsoluteUnit.WithValue(CraftingRecipeSlot.SLOT_SIZE)
+ ))
+ .SetTexture(slotTexture)
+ .SetPatches(slotPatches, 4)
+ .SetTextProperties(new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.GetFontType("Hobo"))
+ .SetFontSize(16f)
+ .SetTextAlignment(TextAlignment.Bottom | TextAlignment.Right)
+ )
+ .SetOnMouseIn((recipe) => {
+ if ((int)State < 2) return;
+ _craftingDisplay.SetRecipe(recipe);
+ _craftingDisplayEnabled = true;
+ })
+ .SetOnMouseOut(() => _craftingDisplayEnabled = false);
+
+ _slots = new List<InventorySlot>();
+
+ ItemManagement = new Container(Rect.ScreenFull);
+ Root.AddChild(ItemManagement);
+
+ LoadHotbar();
+
+ Debug.WriteLine("Loaded Game GUI.");
+ }
+
+ public bool Paused { get; private set; }
+ public void TogglePause() {
+ Paused = !Paused;
+ UpdatePauseMenu();
+ }
+
+ private void LoadPauseMenu(ContentManager Content) {
+ _pauseMenu = new PauseMenu(this, Rect.ScreenFull,
+ new Button(new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(250f),
+ AbsoluteUnit.WithValue(56f)
+ ))
+ .SetPivotPoint(new Vector2(.5f))
+ .SetTexture(Content.Load<Texture2D>("sprites/ui/button"))
+ .MakePatches(4)
+ .SetTextProperties(new TextProperties()
+ .SetColor(Color.White)
+ .SetFont(ResourceManager.Fonts.GetFontType("Hobo"))
+ .SetFontSize(24f)
+ .SetTextAlignment(TextAlignment.Center))
+ .SetColorGroup(new ButtonColorGroup(Color.White, Color.Black, Color.Violet, Color.DarkViolet))
+ );
+
+ Root.AddChild(_pauseMenu);
+
+ UpdatePauseMenu();
+ }
+
+ private void LoadTooltipDisplays(ContentManager Content) {
+ _mousePivot = new Container(new Rect(
+ AbsoluteUnit.WithValue(0f)
+ ));
+
+ _itemDisplay = new ItemTooltipDisplay(new Rect(
+ AbsoluteUnit.WithValue(16f),
+ AbsoluteUnit.WithValue(16f),
+ AbsoluteUnit.WithValue(256f),
+ AbsoluteUnit.WithValue(64f)
+ ), tooltipTexture);
+ _itemDisplay.SetPivot(new Vector2(0f, 1f));
+
+ _mousePivot.AddChild(_itemDisplay);
+
+ _craftingDisplay = new CraftingTooltipDisplay(new Rect(
+ AbsoluteUnit.WithValue(16f),
+ AbsoluteUnit.WithValue(16f),
+ AbsoluteUnit.WithValue(256f),
+ AbsoluteUnit.WithValue(64f)
+ ), tooltipTexture);
+ _craftingDisplay.SetPivot(new Vector2(0f, 0f));
+
+ _mousePivot.AddChild(_craftingDisplay);
+ }
+
+ private void LoadHotbar() {
+ if (Hotbar != null) {
+ Hotbar.Dispose();
+ _slots.Clear();
+ }
+
+ Hotbar = new Container(new Rect(
+ new RelativeUnit(0.5f, ItemManagement.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, ItemManagement.GetRect(), RelativeUnit.Orientation.Vertical),
+ AbsoluteUnit.WithValue((HotbarSlots * InventorySlot.SLOT_SIZE) + ((HotbarSlots - 1) * InventorySlot.SLOT_SPACING)),
+ AbsoluteUnit.WithValue(InventorySlot.SLOT_SIZE)
+ ));
+ Hotbar.SetPivot(new Vector2(0.5f, 1f));
+
+ for (int i = 0; i < HotbarSlots; i++) {
+ int slotNumber = i;
+ InventorySlot slot = _slotTemplate.Clone()
+ .SetNewRect(_slotTemplate.GetRect().SetX(AbsoluteUnit.WithValue(i * InventorySlot.SLOT_SIZE + (i * InventorySlot.SLOT_SPACING))))
+ .SetSlot(slotNumber)
+ .SetOnMouseUp((button, point) => {
+ if ((int)State < 1) {
+ HotbarSelection = slotNumber;
+ UpdateSelected();
+ } else {
+ ItemStack itemInSlot = _inventory.GetSlot(slotNumber);
+
+ _inventory.SetSlot(slotNumber, CursorItem);
+ CursorItem = itemInSlot;
+ }
+ });
+ slot.SetPivot(new Vector2(0f, 0f));
+ slot.SetEnabled(true);
+
+ _slots.Add(slot);
+ Hotbar.AddChild(slot);
+ }
+
+ UpdateSelected();
+
+ ItemManagement.AddChild(Hotbar);
+ }
+
+ private void LoadInventoryScreen() {
+ int remainingSlots = _inventory.Capacity - HotbarSlots;
+ int rows = (remainingSlots / HotbarSlots);
+
+ Container pivot = new Container(new Rect(
+ new RelativeUnit(0.5f, ItemManagement.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, ItemManagement.GetRect(), RelativeUnit.Orientation.Vertical),
+ new AbsoluteUnit((HotbarSlots * InventorySlot.SLOT_SIZE) + ((HotbarSlots + 1) * InventorySlot.SLOT_SPACING)),
+ new AbsoluteUnit((rows * InventorySlot.SLOT_SIZE) + ((rows + 1) * InventorySlot.SLOT_SPACING))
+ ));
+
+ _inventoryScreen = new InventoryWindow(this, new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(-(InventorySlot.SLOT_SIZE + (2 * InventorySlot.SLOT_SPACING))),
+ new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Vertical)
+ ), windowTexture, _inventory, remainingSlots, HotbarSlots, _slotTemplate);
+ _inventoryScreen.SetPivot(new Vector2(0.5f, 1f));
+
+ pivot.AddChild(_inventoryScreen);
+ ItemManagement.AddChild(pivot);
+ }
+
+ private void LoadCraftingScreen() {
+ int remainingSlots = _inventory.Capacity - HotbarSlots;
+ int rows = (remainingSlots / HotbarSlots);
+
+ Container pivot = new Container(new Rect(
+ new RelativeUnit(0.5f, ItemManagement.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(0f, ItemManagement.GetRect(), RelativeUnit.Orientation.Vertical),
+ new AbsoluteUnit((HotbarSlots * CraftingRecipeSlot.SLOT_SIZE) + ((HotbarSlots + 1) * CraftingRecipeSlot.SLOT_SPACING)),
+ new AbsoluteUnit((rows * CraftingRecipeSlot.SLOT_SIZE) + ((rows + 1) * CraftingRecipeSlot.SLOT_SPACING))
+ ));
+
+ _craftingScreen = new CraftingWindow(this, new Rect(
+ AbsoluteUnit.WithValue(0f),
+ AbsoluteUnit.WithValue(CraftingRecipeSlot.SLOT_SPACING),
+ new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Horizontal),
+ new RelativeUnit(1f, pivot.GetRect(), RelativeUnit.Orientation.Vertical)
+ ), windowTexture, _inventory, _recipeTemplate);
+ _craftingScreen.SetPivot(new Vector2(0.5f, 0f));
+
+ pivot.AddChild(_craftingScreen);
+ ItemManagement.AddChild(pivot);
+ }
+
+ public override void Update(GameTime gameTime, out bool clickedAnything)
+ {
+ _mousePivot.MoveTo(MouseHelper.Position);
+ _itemDisplay.SetEnabled(_itemDisplayEnabled && (int)_state > 0);
+ _craftingDisplay.SetEnabled(_craftingDisplayEnabled && (int)_state > 1);
+
+ base.Update(gameTime, out clickedAnything);
+ }
+
+ private Color _slightlyTransparent = new Color(255, 255, 255, 175);
+ private Vector2 scale = new Vector2(2f);
+ public override void Draw(GameTime gameTime)
+ {
+ base.Draw(gameTime);
+
+ Game.SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null);
+
+ if (CursorItem != null) Game.SpriteBatch.Draw(CursorItem.Type.Sprite, MouseHelper.Position.ToVector2(), _slightlyTransparent, 0f, Vector2.Zero, scale, SpriteEffects.None, 0f);
+ else {
+ _itemDisplay.Draw(Game.SpriteBatch);
+ _craftingDisplay.Draw(Game.SpriteBatch);
+ }
+
+ Game.SpriteBatch.End();
+ }
+
+ public ItemStack GetSelectedItem() {
+ return _inventory.GetSlot(HotbarSelection);
+ }
+
+ private void UpdateSelected() {
+ _slots.ForEach(slot => slot.SetSelected(false));
+ _slots[HotbarSelection].SetSelected(true);
+ }
+
+ private void UpdatePauseMenu() {
+ _pauseMenu.SetEnabled(Paused);
+ }
+ }
+
+ public enum InventoryScreenState {
+ Closed, Inventory, Crafting
+ }
+} \ No newline at end of file
diff --git a/source/ui/properties/TextProperties.cs b/source/ui/properties/TextProperties.cs
new file mode 100644
index 0000000..2ee4deb
--- /dev/null
+++ b/source/ui/properties/TextProperties.cs
@@ -0,0 +1,62 @@
+using Celesteia.Resources;
+using Celesteia.Resources.Management;
+using Microsoft.Xna.Framework;
+
+namespace Celesteia.UI.Properties {
+ public struct TextProperties {
+ private string _text;
+ private FontType _font;
+ private Color _textColor;
+ private float _fontSize;
+ private TextAlignment _textAlignment;
+
+ public TextProperties Standard() {
+ _text = "";
+ _font = ResourceManager.Fonts.DEFAULT;
+ _textColor = Color.White;
+ _fontSize = 16f;
+ _textAlignment = TextAlignment.Center;
+
+ return this;
+ }
+
+ public TextProperties SetText(string text) {
+ _text = text;
+ return this;
+ }
+ public string GetText() => _text;
+
+ public TextProperties SetFont(FontType font) {
+ _font = font;
+ return this;
+ }
+ public FontType GetFont() => _font;
+
+ public TextProperties SetColor(Color textColor) {
+ _textColor = textColor;
+ return this;
+ }
+ public Color GetColor() => _textColor;
+
+ public TextProperties SetFontSize(float fontSize) {
+ _fontSize = fontSize;
+ return this;
+ }
+ public float GetFontSize() => _fontSize;
+
+ public TextProperties SetTextAlignment(TextAlignment textAlignment) {
+ _textAlignment = textAlignment;
+ return this;
+ }
+ public TextAlignment GetAlignment() => _textAlignment;
+
+ public TextProperties Clone() {
+ return new TextProperties()
+ .SetColor(_textColor)
+ .SetFont(_font)
+ .SetFontSize(_fontSize)
+ .SetText(_text)
+ .SetTextAlignment(_textAlignment);
+ }
+ }
+} \ No newline at end of file