From 567c422f8cd42eba2437f9a8c2522716a1649be7 Mon Sep 17 00:00:00 2001 From: hazel Date: Mon, 26 Jan 2026 22:04:39 +0100 Subject: celesteia archive, last updated april 9th 2024 Signed-off-by: hazel --- .config/dotnet-tools.json | 36 + .github/workflows/dotnet.yml | 22 + .gitignore | 59 + .mgstats | 1 + .vscode/launch.json | 58 + .vscode/tasks.json | 71 + Celesteia.csproj | 46 + Content/Content.mgcb | 208 ++ Content/Hobo.spritefont | 60 + Content/branding/leafal/leafal-text-logo.png | Bin 0 -> 22619 bytes Content/branding/leafal/splash.mp3 | Bin 0 -> 73828 bytes Content/branding/leafal/splash.mp3.sfk | Bin 0 -> 4528 bytes Content/celesteia/title-rebrand-borders.png | Bin 0 -> 1160 bytes Content/music/Landing Light.ogg | Bin 0 -> 4583221 bytes Content/music/Stargaze Symphony.ogg | Bin 0 -> 4095788 bytes Content/sprites/blockbreak.png | Bin 0 -> 193 bytes Content/sprites/blocks.png | Bin 0 -> 2845 bytes Content/sprites/blockselection.png | Bin 0 -> 139 bytes Content/sprites/crashed_capsule.png | Bin 0 -> 498 bytes Content/sprites/entities/player/astronaut.png | Bin 0 -> 385 bytes Content/sprites/items.png | Bin 0 -> 3536 bytes Content/sprites/skybox/nebula.png | Bin 0 -> 191878 bytes Content/sprites/skybox/shadow.png | Bin 0 -> 535592 bytes Content/sprites/skybox/stars.png | Bin 0 -> 7772 bytes Content/sprites/skybox/stars2.png | Bin 0 -> 19140 bytes Content/sprites/ui.png | Bin 0 -> 1828 bytes Content/sprites/ui/button.png | Bin 0 -> 234 bytes Content/sprites/ui/window.png | Bin 0 -> 217 bytes Content/ttf/hobo.ttf | Bin 0 -> 62116 bytes GameInstance.cs | 151 ++ Icon.bmp | Bin 0 -> 16534 bytes Icon.ico | Bin 0 -> 142862 bytes LICENSE | 21 + Program.cs | 14 + README.md | 8 + app.manifest | 43 + img/background.jpg | Bin 0 -> 49782 bytes img/banner.jpg | Bin 0 -> 34286 bytes img/thumbnail.jpg | Bin 0 -> 45636 bytes source/game/WorldManager.cs | 33 + source/game/components/CameraFollow.cs | 5 + source/game/components/EntityAttributes.cs | 35 + source/game/components/TargetPosition.cs | 7 + source/game/components/entity/GameWorldEntity.cs | 14 + source/game/components/items/Inventory.cs | 96 + source/game/components/items/ItemStack.cs | 34 + source/game/components/physics/CollisionBox.cs | 36 + source/game/components/physics/PhysicsEntity.cs | 38 + source/game/components/player/LocalPlayer.cs | 5 + source/game/components/player/PlayerInput.cs | 14 + source/game/components/skybox/SkyboxRotateZ.cs | 17 + source/game/ecs/EntityFactory.cs | 111 + source/game/ecs/GameWorld.cs | 41 + source/game/input/GamepadHelper.cs | 59 + source/game/input/InputManager.cs | 33 + source/game/input/KeyboardHelper.cs | 53 + source/game/input/MouseHelper.cs | 37 + source/game/input/conditions/AllCondition.cs | 15 + source/game/input/conditions/AnyCondition.cs | 15 + source/game/input/conditions/AverageCondition.cs | 16 + source/game/input/conditions/ICondition.cs | 5 + source/game/input/definitions/InputDefinition.cs | 16 + .../definitions/gamepad/BinaryGamepadDefinition.cs | 10 + .../definitions/gamepad/SensorGamepadDefinition.cs | 13 + .../keyboard/BinaryKeyboardDefinition.cs | 11 + .../keyboard/TrinaryKeyboardDefinition.cs | 18 + .../definitions/mouse/BinaryMouseDefinition.cs | 10 + source/game/items/BlockItemActions.cs | 73 + source/game/items/CooldownItemActions.cs | 17 + source/game/items/FoliageItemActions.cs | 18 + source/game/items/IItemActions.cs | 11 + source/game/items/PickaxeItemActions.cs | 63 + source/game/items/TorchItemActions.cs | 15 + source/game/items/UpgradeItemActions.cs | 48 + source/game/music/MusicManager.cs | 54 + source/game/planets/BlockState.cs | 43 + source/game/planets/Chunk.cs | 158 ++ source/game/planets/ChunkMap.cs | 99 + source/game/planets/GeneratedPlanet.cs | 35 + source/game/planets/generation/IWorldGenerator.cs | 18 + .../planets/generation/TerranPlanetGenerator.cs | 275 +++ source/game/systems/CameraRenderSystem.cs | 43 + source/game/systems/CameraSystem.cs | 39 + source/game/systems/ChunkMapRenderSystem.cs | 63 + source/game/systems/EntityDebugSystem.cs | 44 + source/game/systems/LightingSystem.cs | 100 + source/game/systems/LocalPlayerSystem.cs | 246 ++ source/game/systems/TargetPositionSystem.cs | 37 + .../systems/mainmenu/MainMenuBackgroundSystem.cs | 33 + .../game/systems/mainmenu/MainMenuRenderSystem.cs | 43 + .../systems/physics/PhysicsCollisionDebugSystem.cs | 61 + source/game/systems/physics/PhysicsSystem.cs | 42 + .../systems/physics/PhysicsWorldCollisionSystem.cs | 83 + source/game/systems/ui/GameGUIDrawSystem.cs | 12 + source/graphics/Camera2D.cs | 63 + source/graphics/GraphicsManager.cs | 65 + source/graphics/lighting/LightMap.cs | 139 ++ source/lib/FastNoiseLite.cs | 2506 ++++++++++++++++++++ source/resources/Resources.cs | 81 + source/resources/collections/BaseCollection.cs | 207 ++ source/resources/management/BlockManager.cs | 77 + source/resources/management/EntityManager.cs | 46 + source/resources/management/FontManager.cs | 43 + source/resources/management/ItemManager.cs | 47 + source/resources/management/RecipeManager.cs | 34 + source/resources/management/SkyboxAssets.cs | 42 + source/resources/sprites/BlockFrames.cs | 53 + source/resources/sprites/EntityFrames.cs | 32 + source/resources/sprites/SkyboxPortionFrames.cs | 51 + source/resources/types/BlockType.cs | 71 + source/resources/types/EntityType.cs | 20 + source/resources/types/ItemType.cs | 21 + source/resources/types/Recipe.cs | 48 + source/resources/types/TileEntityType.cs | 27 + .../resources/types/builders/BlockTypeBuilder.cs | 82 + source/resources/types/builders/ItemTypeBuilder.cs | 67 + source/screens/GameplayScreen.cs | 81 + source/screens/MainMenuScreen.cs | 85 + source/screens/SplashScreen.cs | 100 + source/screens/TextTestScreen.cs | 73 + source/ui/Rect.cs | 188 ++ source/ui/TextAlignment.cs | 10 + source/ui/UI.cs | 81 + source/ui/elements/Button.cs | 173 ++ source/ui/elements/Clickable.cs | 21 + source/ui/elements/Container.cs | 91 + source/ui/elements/Element.cs | 53 + source/ui/elements/IClickable.cs | 9 + source/ui/elements/IContainer.cs | 12 + source/ui/elements/IElement.cs | 52 + source/ui/elements/Image.cs | 59 + source/ui/elements/Label.cs | 62 + source/ui/elements/game/CraftingRecipeSlot.cs | 173 ++ source/ui/elements/game/CraftingWindow.cs | 62 + source/ui/elements/game/InventorySlot.cs | 191 ++ source/ui/elements/game/InventoryWindow.cs | 56 + source/ui/elements/game/PauseMenu.cs | 74 + source/ui/elements/game/controls/ControlTips.cs | 51 + .../game/tooltips/CraftingTooltipDisplay.cs | 97 + source/ui/elements/game/tooltips/ItemDisplay.cs | 21 + .../elements/game/tooltips/ItemTooltipDisplay.cs | 62 + source/ui/elements/game/tooltips/TooltipDisplay.cs | 8 + source/ui/guis/DebugGUI.cs | 66 + source/ui/guis/GUI.cs | 48 + source/ui/guis/MainMenu.cs | 225 ++ source/ui/guis/game/GameGUI.cs | 349 +++ source/ui/properties/TextProperties.cs | 62 + 147 files changed, 9885 insertions(+) create mode 100644 .config/dotnet-tools.json create mode 100644 .github/workflows/dotnet.yml create mode 100644 .gitignore create mode 100644 .mgstats create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 Celesteia.csproj create mode 100644 Content/Content.mgcb create mode 100644 Content/Hobo.spritefont create mode 100644 Content/branding/leafal/leafal-text-logo.png create mode 100644 Content/branding/leafal/splash.mp3 create mode 100644 Content/branding/leafal/splash.mp3.sfk create mode 100644 Content/celesteia/title-rebrand-borders.png create mode 100644 Content/music/Landing Light.ogg create mode 100644 Content/music/Stargaze Symphony.ogg create mode 100644 Content/sprites/blockbreak.png create mode 100644 Content/sprites/blocks.png create mode 100644 Content/sprites/blockselection.png create mode 100644 Content/sprites/crashed_capsule.png create mode 100644 Content/sprites/entities/player/astronaut.png create mode 100644 Content/sprites/items.png create mode 100644 Content/sprites/skybox/nebula.png create mode 100644 Content/sprites/skybox/shadow.png create mode 100644 Content/sprites/skybox/stars.png create mode 100644 Content/sprites/skybox/stars2.png create mode 100644 Content/sprites/ui.png create mode 100644 Content/sprites/ui/button.png create mode 100644 Content/sprites/ui/window.png create mode 100644 Content/ttf/hobo.ttf create mode 100644 GameInstance.cs create mode 100644 Icon.bmp create mode 100644 Icon.ico create mode 100644 LICENSE create mode 100644 Program.cs create mode 100644 README.md create mode 100644 app.manifest create mode 100644 img/background.jpg create mode 100644 img/banner.jpg create mode 100644 img/thumbnail.jpg create mode 100644 source/game/WorldManager.cs create mode 100644 source/game/components/CameraFollow.cs create mode 100644 source/game/components/EntityAttributes.cs create mode 100644 source/game/components/TargetPosition.cs create mode 100644 source/game/components/entity/GameWorldEntity.cs create mode 100644 source/game/components/items/Inventory.cs create mode 100644 source/game/components/items/ItemStack.cs create mode 100644 source/game/components/physics/CollisionBox.cs create mode 100644 source/game/components/physics/PhysicsEntity.cs create mode 100644 source/game/components/player/LocalPlayer.cs create mode 100644 source/game/components/player/PlayerInput.cs create mode 100644 source/game/components/skybox/SkyboxRotateZ.cs create mode 100644 source/game/ecs/EntityFactory.cs create mode 100644 source/game/ecs/GameWorld.cs create mode 100644 source/game/input/GamepadHelper.cs create mode 100644 source/game/input/InputManager.cs create mode 100644 source/game/input/KeyboardHelper.cs create mode 100644 source/game/input/MouseHelper.cs create mode 100644 source/game/input/conditions/AllCondition.cs create mode 100644 source/game/input/conditions/AnyCondition.cs create mode 100644 source/game/input/conditions/AverageCondition.cs create mode 100644 source/game/input/conditions/ICondition.cs create mode 100644 source/game/input/definitions/InputDefinition.cs create mode 100644 source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs create mode 100644 source/game/input/definitions/gamepad/SensorGamepadDefinition.cs create mode 100644 source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs create mode 100644 source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs create mode 100644 source/game/input/definitions/mouse/BinaryMouseDefinition.cs create mode 100644 source/game/items/BlockItemActions.cs create mode 100644 source/game/items/CooldownItemActions.cs create mode 100644 source/game/items/FoliageItemActions.cs create mode 100644 source/game/items/IItemActions.cs create mode 100644 source/game/items/PickaxeItemActions.cs create mode 100644 source/game/items/TorchItemActions.cs create mode 100644 source/game/items/UpgradeItemActions.cs create mode 100644 source/game/music/MusicManager.cs create mode 100644 source/game/planets/BlockState.cs create mode 100644 source/game/planets/Chunk.cs create mode 100644 source/game/planets/ChunkMap.cs create mode 100644 source/game/planets/GeneratedPlanet.cs create mode 100644 source/game/planets/generation/IWorldGenerator.cs create mode 100644 source/game/planets/generation/TerranPlanetGenerator.cs create mode 100644 source/game/systems/CameraRenderSystem.cs create mode 100644 source/game/systems/CameraSystem.cs create mode 100644 source/game/systems/ChunkMapRenderSystem.cs create mode 100644 source/game/systems/EntityDebugSystem.cs create mode 100644 source/game/systems/LightingSystem.cs create mode 100644 source/game/systems/LocalPlayerSystem.cs create mode 100644 source/game/systems/TargetPositionSystem.cs create mode 100644 source/game/systems/mainmenu/MainMenuBackgroundSystem.cs create mode 100644 source/game/systems/mainmenu/MainMenuRenderSystem.cs create mode 100644 source/game/systems/physics/PhysicsCollisionDebugSystem.cs create mode 100644 source/game/systems/physics/PhysicsSystem.cs create mode 100644 source/game/systems/physics/PhysicsWorldCollisionSystem.cs create mode 100644 source/game/systems/ui/GameGUIDrawSystem.cs create mode 100644 source/graphics/Camera2D.cs create mode 100644 source/graphics/GraphicsManager.cs create mode 100644 source/graphics/lighting/LightMap.cs create mode 100644 source/lib/FastNoiseLite.cs create mode 100644 source/resources/Resources.cs create mode 100644 source/resources/collections/BaseCollection.cs create mode 100644 source/resources/management/BlockManager.cs create mode 100644 source/resources/management/EntityManager.cs create mode 100644 source/resources/management/FontManager.cs create mode 100644 source/resources/management/ItemManager.cs create mode 100644 source/resources/management/RecipeManager.cs create mode 100644 source/resources/management/SkyboxAssets.cs create mode 100644 source/resources/sprites/BlockFrames.cs create mode 100644 source/resources/sprites/EntityFrames.cs create mode 100644 source/resources/sprites/SkyboxPortionFrames.cs create mode 100644 source/resources/types/BlockType.cs create mode 100644 source/resources/types/EntityType.cs create mode 100644 source/resources/types/ItemType.cs create mode 100644 source/resources/types/Recipe.cs create mode 100644 source/resources/types/TileEntityType.cs create mode 100644 source/resources/types/builders/BlockTypeBuilder.cs create mode 100644 source/resources/types/builders/ItemTypeBuilder.cs create mode 100644 source/screens/GameplayScreen.cs create mode 100644 source/screens/MainMenuScreen.cs create mode 100644 source/screens/SplashScreen.cs create mode 100644 source/screens/TextTestScreen.cs create mode 100644 source/ui/Rect.cs create mode 100644 source/ui/TextAlignment.cs create mode 100644 source/ui/UI.cs create mode 100644 source/ui/elements/Button.cs create mode 100644 source/ui/elements/Clickable.cs create mode 100644 source/ui/elements/Container.cs create mode 100644 source/ui/elements/Element.cs create mode 100644 source/ui/elements/IClickable.cs create mode 100644 source/ui/elements/IContainer.cs create mode 100644 source/ui/elements/IElement.cs create mode 100644 source/ui/elements/Image.cs create mode 100644 source/ui/elements/Label.cs create mode 100644 source/ui/elements/game/CraftingRecipeSlot.cs create mode 100644 source/ui/elements/game/CraftingWindow.cs create mode 100644 source/ui/elements/game/InventorySlot.cs create mode 100644 source/ui/elements/game/InventoryWindow.cs create mode 100644 source/ui/elements/game/PauseMenu.cs create mode 100644 source/ui/elements/game/controls/ControlTips.cs create mode 100644 source/ui/elements/game/tooltips/CraftingTooltipDisplay.cs create mode 100644 source/ui/elements/game/tooltips/ItemDisplay.cs create mode 100644 source/ui/elements/game/tooltips/ItemTooltipDisplay.cs create mode 100644 source/ui/elements/game/tooltips/TooltipDisplay.cs create mode 100644 source/ui/guis/DebugGUI.cs create mode 100644 source/ui/guis/GUI.cs create mode 100644 source/ui/guis/MainMenu.cs create mode 100644 source/ui/guis/game/GameGUI.cs create mode 100644 source/ui/properties/TextProperties.cs 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 @@ + + + WinExe + net6.0 + Major + false + false + + + app.manifest + Icon.ico + + + none + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + ttf/hobo.ttf + + + 12 + + + 0 + + + true + + + + + + + + + + + + ~ + + + + 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 Binary files /dev/null and b/Content/branding/leafal/leafal-text-logo.png differ diff --git a/Content/branding/leafal/splash.mp3 b/Content/branding/leafal/splash.mp3 new file mode 100644 index 0000000..61149a4 Binary files /dev/null and b/Content/branding/leafal/splash.mp3 differ diff --git a/Content/branding/leafal/splash.mp3.sfk b/Content/branding/leafal/splash.mp3.sfk new file mode 100644 index 0000000..cdde5aa Binary files /dev/null and b/Content/branding/leafal/splash.mp3.sfk differ diff --git a/Content/celesteia/title-rebrand-borders.png b/Content/celesteia/title-rebrand-borders.png new file mode 100644 index 0000000..6698d44 Binary files /dev/null and b/Content/celesteia/title-rebrand-borders.png differ diff --git a/Content/music/Landing Light.ogg b/Content/music/Landing Light.ogg new file mode 100644 index 0000000..9020790 Binary files /dev/null and b/Content/music/Landing Light.ogg differ diff --git a/Content/music/Stargaze Symphony.ogg b/Content/music/Stargaze Symphony.ogg new file mode 100644 index 0000000..232848a Binary files /dev/null and b/Content/music/Stargaze Symphony.ogg differ diff --git a/Content/sprites/blockbreak.png b/Content/sprites/blockbreak.png new file mode 100644 index 0000000..27e0e35 Binary files /dev/null and b/Content/sprites/blockbreak.png differ diff --git a/Content/sprites/blocks.png b/Content/sprites/blocks.png new file mode 100644 index 0000000..81c183a Binary files /dev/null and b/Content/sprites/blocks.png differ diff --git a/Content/sprites/blockselection.png b/Content/sprites/blockselection.png new file mode 100644 index 0000000..81025a4 Binary files /dev/null and b/Content/sprites/blockselection.png differ diff --git a/Content/sprites/crashed_capsule.png b/Content/sprites/crashed_capsule.png new file mode 100644 index 0000000..334d877 Binary files /dev/null and b/Content/sprites/crashed_capsule.png differ diff --git a/Content/sprites/entities/player/astronaut.png b/Content/sprites/entities/player/astronaut.png new file mode 100644 index 0000000..6d9876f Binary files /dev/null and b/Content/sprites/entities/player/astronaut.png differ diff --git a/Content/sprites/items.png b/Content/sprites/items.png new file mode 100644 index 0000000..27b9505 Binary files /dev/null and b/Content/sprites/items.png differ diff --git a/Content/sprites/skybox/nebula.png b/Content/sprites/skybox/nebula.png new file mode 100644 index 0000000..da2b259 Binary files /dev/null and b/Content/sprites/skybox/nebula.png differ diff --git a/Content/sprites/skybox/shadow.png b/Content/sprites/skybox/shadow.png new file mode 100644 index 0000000..ca44b51 Binary files /dev/null and b/Content/sprites/skybox/shadow.png differ diff --git a/Content/sprites/skybox/stars.png b/Content/sprites/skybox/stars.png new file mode 100644 index 0000000..22b5a67 Binary files /dev/null and b/Content/sprites/skybox/stars.png differ diff --git a/Content/sprites/skybox/stars2.png b/Content/sprites/skybox/stars2.png new file mode 100644 index 0000000..24e3809 Binary files /dev/null and b/Content/sprites/skybox/stars2.png differ diff --git a/Content/sprites/ui.png b/Content/sprites/ui.png new file mode 100644 index 0000000..5e25510 Binary files /dev/null and b/Content/sprites/ui.png differ diff --git a/Content/sprites/ui/button.png b/Content/sprites/ui/button.png new file mode 100644 index 0000000..b1a3671 Binary files /dev/null and b/Content/sprites/ui/button.png differ diff --git a/Content/sprites/ui/window.png b/Content/sprites/ui/window.png new file mode 100644 index 0000000..8f2bf06 Binary files /dev/null and b/Content/sprites/ui/window.png differ diff --git a/Content/ttf/hobo.ttf b/Content/ttf/hobo.ttf new file mode 100644 index 0000000..fd7a473 Binary files /dev/null and b/Content/ttf/hobo.ttf 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 cmdArgs; + + private List 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(); + + 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 Binary files /dev/null and b/Icon.bmp differ diff --git a/Icon.ico b/Icon.ico new file mode 100644 index 0000000..b2ffd7e Binary files /dev/null and b/Icon.ico 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + + diff --git a/img/background.jpg b/img/background.jpg new file mode 100644 index 0000000..30cee80 Binary files /dev/null and b/img/background.jpg differ diff --git a/img/banner.jpg b/img/banner.jpg new file mode 100644 index 0000000..91920f9 Binary files /dev/null and b/img/banner.jpg differ diff --git a/img/thumbnail.jpg b/img/thumbnail.jpg new file mode 100644 index 0000000..c1285c8 Binary files /dev/null and b/img/thumbnail.jpg 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 LoadNewWorld(Action progressReport = null, int? seed = null) { + // Asynchronously generate the world. + GameWorld generatedWorld = await Task.Run(() => { + 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().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 Horizontal; + public ICondition Run; + public ICondition Jump; + public ICondition Inventory; + public ICondition Crafting; + public ICondition Pause; + public ICondition PrimaryUse; + public ICondition 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()) + 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 exemptedFromAny = new List { + { 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 { + 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 { + 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 { + 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 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 Test(); + } + + public interface IBinaryInputDefinition : IInputDefinition {} + public interface IFloatInputDefinition : IInputDefinition {} + + 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() || !user.Has()) return false; + + Transform2 entityTransform = user.Get(); + EntityAttributes.EntityAttributeMap attributes = user.Get().Attributes; + + if (Vector2.Distance(entityTransform.Position, cursor.ToVector2()) > attributes.Get(EntityAttribute.BlockRange)) return false; + + if (!forWall && user.Has()) { + Rectangle box = user.Get().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() || !user.Has()) return false; + + Transform2 entityTransform = user.Get(); + EntityAttributes.EntityAttributeMap attributes = user.Get().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()) + user.Get().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()) return false; + + EntityAttributes.EntityAttributeMap attributes = user.Get().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().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 progressReport = null) { + Action 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 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 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 foliageDistribution = new Dictionary { + { 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 transformMapper; + private ComponentMapper 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(); + entityFramesMapper = mapperService.GetMapper(); + } + + 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 transformMapper; + + public CameraSystem(Camera2D camera) : base(Aspect.All(typeof(Transform2), typeof(CameraFollow))) + => _camera = camera; + + public override void Initialize(IComponentMapperService mapperService) + => transformMapper = mapperService.GetMapper(); + + 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 activeChunks = new List(); + 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 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(); + + _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 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(); + } + + 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(_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(); + targetPosition = _player.Get(); + physicsEntity = _player.Get(); + frames = _player.Get(); + attributes = _player.Get(); + input = _player.Get(); + inventory = _player.Get(); + } + } + 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 hotbarMappings = new Dictionary() { + { (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 transformMapper; + private ComponentMapper targetPositionMapper; + + public TargetPositionSystem(ChunkMap chunkMap) : base(Aspect.All(typeof(Transform2), typeof(TargetPosition))) + => _chunkMap = chunkMap; + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + targetPositionMapper = mapperService.GetMapper(); + } + + 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 transformMapper; + private ComponentMapper rotatorMapper; + + public MainMenuBackgroundSystem() : base(Aspect.All(typeof(Transform2), typeof(SkyboxRotateZ))) {} + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + rotatorMapper = mapperService.GetMapper(); + } + + 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 transformMapper; + private ComponentMapper 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(); + framesMapper = mapperService.GetMapper(); + } + + 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 transformMapper; + private ComponentMapper 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(); + collisionBoxMapper = mapperService.GetMapper(); + } + + 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 targetPositionMapper; + private ComponentMapper physicsEntityMapper; + + public override void Initialize(IComponentMapperService mapperService) + { + targetPositionMapper = mapperService.GetMapper(); + physicsEntityMapper = mapperService.GetMapper(); + } + + 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 transformMapper; + private ComponentMapper targetPositionMapper; + private ComponentMapper physicsEntityMapper; + private ComponentMapper collisionBoxMapper; + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + targetPositionMapper = mapperService.GetMapper(); + physicsEntityMapper = mapperService.GetMapper(); + collisionBoxMapper = mapperService.GetMapper(); + } + + 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; + + /// + /// Create new FastNoise object with optional seed + /// + public FastNoiseLite(int seed = 1337) + { + SetSeed(seed); + } + + /// + /// Sets seed used for all noise types + /// + /// + /// Default: 1337 + /// + public void SetSeed(int seed) { mSeed = seed; } + + /// + /// Sets frequency for all noise types + /// + /// + /// Default: 0.01 + /// + public void SetFrequency(float frequency) { mFrequency = frequency; } + + /// + /// Sets noise algorithm used for GetNoise(...) + /// + /// + /// Default: OpenSimplex2 + /// + public void SetNoiseType(NoiseType noiseType) + { + mNoiseType = noiseType; + UpdateTransformType3D(); + } + + /// + /// Sets domain rotation type for 3D Noise and 3D DomainWarp. + /// Can aid in reducing directional artifacts when sampling a 2D plane in 3D + /// + /// + /// Default: None + /// + public void SetRotationType3D(RotationType3D rotationType3D) + { + mRotationType3D = rotationType3D; + UpdateTransformType3D(); + UpdateWarpTransformType3D(); + } + + /// + /// Sets method for combining octaves in all fractal noise types + /// + /// + /// Default: None + /// Note: FractalType.DomainWarp... only affects DomainWarp(...) + /// + public void SetFractalType(FractalType fractalType) { mFractalType = fractalType; } + + /// + /// Sets octave count for all fractal noise types + /// + /// + /// Default: 3 + /// + public void SetFractalOctaves(int octaves) + { + mOctaves = octaves; + CalculateFractalBounding(); + } + + /// + /// Sets octave lacunarity for all fractal noise types + /// + /// + /// Default: 2.0 + /// + public void SetFractalLacunarity(float lacunarity) { mLacunarity = lacunarity; } + + /// + /// Sets octave gain for all fractal noise types + /// + /// + /// Default: 0.5 + /// + public void SetFractalGain(float gain) + { + mGain = gain; + CalculateFractalBounding(); + } + + /// + /// Sets octave weighting for all none DomainWarp fratal types + /// + /// + /// Default: 0.0 + /// Note: Keep between 0...1 to maintain -1...1 output bounding + /// + public void SetFractalWeightedStrength(float weightedStrength) { mWeightedStrength = weightedStrength; } + + /// + /// Sets strength of the fractal ping pong effect + /// + /// + /// Default: 2.0 + /// + public void SetFractalPingPongStrength(float pingPongStrength) { mPingPongStrength = pingPongStrength; } + + + /// + /// Sets distance function used in cellular noise calculations + /// + /// + /// Default: Distance + /// + public void SetCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) { mCellularDistanceFunction = cellularDistanceFunction; } + + /// + /// Sets return type from cellular noise calculations + /// + /// + /// Default: EuclideanSq + /// + public void SetCellularReturnType(CellularReturnType cellularReturnType) { mCellularReturnType = cellularReturnType; } + + /// + /// Sets the maximum distance a cellular point can move from it's grid position + /// + /// + /// Default: 1.0 + /// Note: Setting this higher than 1 will cause artifacts + /// + public void SetCellularJitter(float cellularJitter) { mCellularJitterModifier = cellularJitter; } + + + /// + /// Sets the warp algorithm when using DomainWarp(...) + /// + /// + /// Default: OpenSimplex2 + /// + public void SetDomainWarpType(DomainWarpType domainWarpType) + { + mDomainWarpType = domainWarpType; + UpdateWarpTransformType3D(); + } + + + /// + /// Sets the maximum warp distance from original position when using DomainWarp(...) + /// + /// + /// Default: 1.0 + /// + public void SetDomainWarpAmp(float domainWarpAmp) { mDomainWarpAmp = domainWarpAmp; } + + + /// + /// 2D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + [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); + } + } + + /// + /// 3D noise at given position using current settings + /// + /// + /// Noise output bounded between -1...1 + /// + [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); + } + } + + + /// + /// 2D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(ref x, ref y) + /// noise = GetNoise(x, y) + /// + [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; + } + } + + /// + /// 3D warps the input position using current domain warp settings + /// + /// + /// Example usage with GetNoise + /// DomainWarp(ref x, ref y, ref z) + /// noise = GetNoise(x, y, z) + /// + [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 GetBlocks(); + public Dictionary GetItems(); + public Dictionary GetRecipes(); + public Dictionary 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 GetBlocks() => LoadBlocks(); + private Dictionary blocks; + private Dictionary 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("sprites/blocks"), pixelSize, pixelSize); + BlockTypeBuilder builder = new BlockTypeBuilder(_atlas); + + blocks = new Dictionary(); + 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("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 GetItems() => LoadItems(); + private Dictionary items; + private Dictionary 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("sprites/items"), pixelSize, pixelSize); + ItemTypeBuilder builder = new ItemTypeBuilder(_atlas); + + items = new Dictionary(); + if (blocks != null) { + foreach (KeyValuePair 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 GetRecipes() => LoadRecipes(); + private Dictionary recipes; + private Dictionary LoadRecipes() { + if (recipes != null) return recipes; + + void AddRecipe(string index, Recipe type) => recipes.Add(GetKey(index), type); + + recipes = new Dictionary(); + 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 GetEntities() => LoadEntities(); + private Dictionary entities; + private Dictionary LoadEntities() { + if (entities != null) return entities; + + void AddEntity(string index, EntityType type) => entities.Add(GetKey(index), type); + + entities = new Dictionary(); + AddEntity("player", new EntityType((e) => EntityFactory.BuildPlayer(e, _content.Load("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 Types; + private BlockType[] BakedTypes; + private Dictionary keys = new Dictionary(); + + 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("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("sprites/blockselection"), + BlockSpriteProperties.SIZE, + BlockSpriteProperties.SIZE + ), 0, 1); + } + + private List _collections = new List(); + 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(); + + 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 Types; + private EntityType[] BakedTypes; + private Dictionary keys = new Dictionary(); + + private List _collections = new List(); + public void AddCollection(IResourceCollection collection) => _collections.Add(collection); + + public void LoadContent(ContentManager Content) { + Debug.WriteLine($"Loading entity types..."); + + Types = new List(); + + 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 Types; + + public void LoadContent(ContentManager Content) { + Debug.WriteLine($"Loading fonts..."); + + Types = new List(); + + Types.Add(DEFAULT = new FontType("Hobo", Content.Load("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 Types; + private ItemType[] BakedTypes; + private Dictionary keys = new Dictionary(); + + private List _collections = new List(); + public void AddCollection(IResourceCollection collection) => _collections.Add(collection); + + public void LoadContent(ContentManager Content) { + Debug.WriteLine($"Loading item types..."); + + Types = new List(); + + 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 Recipes; + + private List _collections = new List(); + public void AddCollection(IResourceCollection collection) => _collections.Add(collection); + + public void LoadContent(ContentManager Content) { + Debug.WriteLine($"Loading crafting recipes..."); + + Recipes = new List(); + + 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 Assets; + + public void LoadContent(ContentManager Content) { + Debug.WriteLine($"Loading skybox assets..."); + + Assets = new List(); + + Assets.Add(new SkyboxAsset(0, "stars", TextureAtlas.Create("stars", Content.Load("sprites/skybox/stars2"), 1024, 1024), 1024, 0)); + Assets.Add(new SkyboxAsset(1, "shadow", TextureAtlas.Create("shadow", Content.Load("sprites/skybox/shadow"), 1024, 1024), 1024, 0)); + Assets.Add(new SkyboxAsset(2, "nebula", TextureAtlas.Create("nebula", Content.Load("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 InstantiateAction; + + public EntityType(Action 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 Ingredients; + + public Recipe(Part result, params Part[] ingredients) { + Result = result; + Ingredients = new List(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 InstantiateAction; + + public TileEntityType(NamespacedKey part, Action 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("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().Target = _chunkMap.GetSpawnpoint(); + _gameGui.SetReferenceInventory(player.Get()); + 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("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("branding/leafal/leafal_text_logo"); + splashSound = Game.Content.Load("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 Children; + + public Container(Rect rect) { + SetRect(rect); + Children = new List(); + } + + public void AddChild(IElement element) { + Children.Add(element); + element.SetParent(this); + } + + public List 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 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 _keyboardControls = new Dictionary(); + private List _lines = new List(); + private List