diff --git a/evolve-die-repeat/ATTRIBUTION.md b/evolve-die-repeat/ATTRIBUTION.md
new file mode 100644
index 0000000..a7864f6
--- /dev/null
+++ b/evolve-die-repeat/ATTRIBUTION.md
@@ -0,0 +1,38 @@
+# Attribution
+## Collaborators
+
+### Role
+Person 1
+Person 2
+[Person w/ Link]()
+
+## Sourced
+### Asset Type
+#### Use Case
+Author: [Name]()
+Source: [Domain : webpage.html]()
+License: [License]()
+
+#### Godot Engine Logo
+Author: Andrea Calabró
+Source: [godotengine.org : press](https://godotengine.org/press/)
+License: [CC BY 4.0 International](https://github.com/godotengine/godot/blob/master/LOGO_LICENSE.txt)
+
+## Tools
+#### Godot
+
+Author: [Juan Linietsky, Ariel Manzur, and contributors](https://godotengine.org/contact)
+Source: [godotengine.org](https://godotengine.org/)
+License: [MIT License](https://github.com/godotengine/godot/blob/master/LICENSE.txt)
+
+#### Godot Game Template
+
+Author: [Marek Belski and contributors](https://github.com/Maaack/Godot-Game-Template/graphs/contributors)
+Source: [github: Godot-Game-Template](https://github.com/Maaack/Godot-Game-Template)
+License: [MIT License](LICENSE.txt)
+
+#### Git
+
+Author: [Linus Torvalds](https://github.com/torvalds)
+Source: [git-scm.com](https://git-scm.com/downloads)
+License: [GNU General Public License version 2](https://opensource.org/licenses/GPL-2.0)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/ATTRIBUTION.md b/evolve-die-repeat/addons/maaacks_game_template/ATTRIBUTION.md
new file mode 100644
index 0000000..c5a52be
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/ATTRIBUTION.md
@@ -0,0 +1,37 @@
+# Attribution
+## Collaborators
+
+### Godot Game Template
+
+Author: [Marek Belski and contributors](https://github.com/Maaack/Godot-Game-Template/graphs/contributors)
+Source: [github: Godot-Game-Template](https://github.com/Maaack/Godot-Game-Template)
+License: [MIT License](LICENSE.txt)
+
+## Sourced
+#### Godot Engine Logo
+Author: Andrea Calabró
+Source: [godotengine.org : press](https://godotengine.org/press/)
+License: [CC BY 4.0 International](https://github.com/godotengine/godot/blob/master/LOGO_LICENSE.txt)
+
+#### Git Logo
+Author: [Jason Long](https://bsky.app/profile/jasonlong.me)
+Source: [git-scm.com : logos](https://git-scm.com/downloads/logos)
+License: [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/)
+
+## Tools
+#### Godot
+
+Author: [Juan Linietsky, Ariel Manzur, and contributors](https://godotengine.org/contact)
+Source: [godotengine.org](https://godotengine.org/)
+License: [MIT License](https://github.com/godotengine/godot/blob/master/LICENSE.txt)
+
+#### Visual Studio Code
+Author: [Microsoft](https://opensource.microsoft.com/)
+Source: [github: vscode](https://github.com/microsoft/vscode)
+License: [MIT License](https://github.com/microsoft/vscode/blob/main/LICENSE.txt)
+
+#### Git
+
+Author: [Linus Torvalds](https://github.com/torvalds)
+Source: [git-scm.com](https://git-scm.com/downloads)
+License: [GNU General Public License version 2](https://opensource.org/licenses/GPL-2.0)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/LICENSE.txt b/evolve-die-repeat/addons/maaacks_game_template/LICENSE.txt
new file mode 100644
index 0000000..935618d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2022-present Marek Belski.
+
+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/evolve-die-repeat/addons/maaacks_game_template/README.md b/evolve-die-repeat/addons/maaacks_game_template/README.md
new file mode 100644
index 0000000..4e24db4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/README.md
@@ -0,0 +1,176 @@
+# Godot Game Template
+For Godot 4.6 (4.3+ compatible)
+
+> [!NOTE]
+> Using the latest version of Godot is recommended.
+> See [Main Menu Setup](/addons/maaacks_game_template/docs/MainMenuSetup.md) for use with versions < 4.6.
+
+This template has a main menu, options menus, pause menu, credits, scene loader, extra tools, and an example game scene.
+
+[Example on itch.io](https://maaack.itch.io/godot-game-template)
+
+[Featured Games](#featured-games)
+
+### Videos
+
+[](https://youtu.be/U9CB3vKINVw)
+[More Videos](/addons/maaacks_game_template/docs/Videos.md)
+
+### Screenshots
+
+
+
+
+
+[More Screenshots](/addons/maaacks_game_template/docs/Screenshots.md)
+
+## Objective
+
+Setup menus and accessibility features in about 15 minutes.
+
+The template can be the start of a new project, or plug into an existing one. It is game agnostic (2D or 3D) and can work with multiple target resolutions, up to 4k and down to 640x360. It's meant to cover the needs for a typical game jam, while remaining scalable and extensible enough to support commercial games.
+
+## Features
+
+### Base
+
+The `base/` folder holds the core components of the menus application.
+
+- Main Menu
+- Options Menus
+- Pause Menu
+- Credits
+- Loading Screen
+- Opening Scene
+- Persistent Settings
+- Simple Config Interface
+- Extensible Overlay Menus
+- Keyboard/Mouse Support
+- Gamepad Support
+- UI Sound Controller
+- Background Music Controller
+- Credits Reader (Markdown File Parser)
+- Global State Management (Basic Saving/Loading)
+- Global Config Autoload
+
+### Extras
+
+The `extras/` folder holds components that extend the core application.
+
+- Level Loaders
+- Level Progress Manager
+- Win / Lose Manager
+- Script for Releasing on [itch.io](https://itch.io/) with [butler](https://itch.io/docs/butler/)
+
+### Examples
+
+The `examples/` folder contains an example project using inherited scenes from the `base/` and `extras/`.
+
+- Game Scene
+- Level Class & 3 Levels
+- Tutorial Windows & 3 Tutorial Messages
+- Win & Lose Windows
+- Master Options Menu
+- End Credits
+- Main Menu w/ Animations
+- Opening w/ Godot Logo
+- Game and Level State Management
+
+### Minimal
+
+Users that want a minimal set of features can try [Maaack's Minimal Game Template](https://github.com/Maaack/Godot-Minimal-Game-Template) or other options from the [plugin suite](/addons/maaacks_game_template/docs/PluginSuite.md).
+
+
+## Installation
+
+### Godot Asset Library
+This package is available as both a template and a plugin, meaning it can be used to start a new project, or added to an existing project.
+
+
+
+When starting a new project:
+
+1. Go to the `Asset Library Projects` tab.
+2. Search for "Maaack's Game Template".
+3. Click on the result to open the template details.
+4. Click to Download.
+5. Give the project a new name and destination.
+6. Click to Install & Edit.
+7. Continue with the [Basic Setup](/addons/maaacks_game_template/docs/BasicSetup.md)
+
+When editing an existing project:
+
+1. Go to the `AssetLib` tab.
+2. Search for "Maaack's Game Template Plugin".
+3. Click on the result to open the plugin details.
+4. Click to Download.
+5. Check that contents are getting installed to `addons/` and there are no conflicts.
+6. Click to Install.
+7. Reload the project (you may see errors before you do this).
+8. Enable the plugin from the Project Settings > Plugins tab.
+ 1. If it's enabled for the first time, the setup wizard will start.
+ 2. Close the window behind it and complete the setup wizard.
+9. Continue with the [Basic Setup](/addons/maaacks_game_template/docs/BasicSetup.md)
+
+
+### GitHub
+
+
+1. Download the latest release version from [GitHub](https://github.com/Maaack/Godot-Game-Template/releases/latest).
+2. Extract the contents of the archive.
+3. Move the `addons/maaacks_game_template` folder into your project's `addons/` folder.
+4. Open/Reload the project.
+5. Enable the plugin from the Project Settings > Plugins tab.
+ 1. If it's enabled for the first time, the setup wizard will start.
+ 2. Close the window behind it and complete the setup wizard.
+6. Continue with the [Basic Setup](/addons/maaacks_game_template/docs/BasicSetup.md)
+
+
+## Usage
+
+[Basic Setup](/addons/maaacks_game_template/docs/BasicSetup.md) is done through the _Setup Wizard_ at `Project > Tools > Run Maaack's Game Template Setup...`.
+
+As part of setup, example scenes are copied out of `/addons/` into a desired folder (project root by default). These can be edited to fit requirements.
+
+### More Documentation
+
+[Main Menu Setup](/addons/maaacks_game_template/docs/MainMenuSetup.md)
+[Options Menu Setup](/addons/maaacks_game_template/docs/OptionsMenuSetup.md)
+[Game Scene Setup](/addons/maaacks_game_template/docs/GameSceneSetup.md)
+[Updating Credits](/addons/maaacks_game_template/docs/UpdatingCredits.md)
+[Blending Music](/addons/maaacks_game_template/docs/BlendingMusic.md)
+[Adding UI Sound Effects](/addons/maaacks_game_template/docs/AddingUISFX.md)
+[Loading Scenes](/addons/maaacks_game_template/docs/LoadingScenes.md)
+[Input Icon Mapping](/addons/maaacks_game_template/docs/InputIconMapping.md)
+[Joypad Inputs](/addons/maaacks_game_template/docs/JoypadInputs.md)
+[Game Saving](/addons/maaacks_game_template/docs/GameSaving.md)
+[How Parts Work](/addons/maaacks_game_template/docs/HowPartsWork.md)
+[Moving Files](/addons/maaacks_game_template/docs/MovingFiles.md)
+[Uploading to itch.io](/addons/maaacks_game_template/docs/UploadingToItchIo.md)
+[Build and Publish Your Game Using CICD](/addons/maaacks_game_template/docs/BuildAndPublish.md)
+[Automatic Updating](/addons/maaacks_game_template/docs/AutomaticUpdating.md)
+[Exhibiting Your Game](/addons/maaacks_game_template/docs/Exhibiting.md)
+
+---
+
+## Featured Games
+
+| HeartFix Express | Baking Godium | Rent Seek Kill |
+| :-------:| :-------: | :-------: |
+|  |  |  |
+| [Find on Steam](https://store.steampowered.com/app/3983290/HeartFix_Express_Demo/) | [Play on itch.io](https://maaack.itch.io/baking-godium) | [Play on itch.io](https://xandruher.itch.io/rent-seek-kill) |
+
+
+[All Shared Games](/addons/maaacks_game_template/docs/GamesMade.md)
+
+
+## Community
+
+Join the [Discord server](https://discord.gg/AyZrJh5AMp ) and share your work with others. It's also a space for getting or giving feedback, and asking for help.
+
+
+## Links
+[Attribution](/addons/maaacks_game_template/ATTRIBUTION.md)
+[License](/addons/maaacks_game_template/LICENSE.txt)
+[Godot Asset Library - Template](https://godotengine.org/asset-library/asset/2703)
+[Godot Asset Library - Plugin](https://godotengine.org/asset-library/asset/2709)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png b/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png
new file mode 100644
index 0000000..18c5b29
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png.import
new file mode 100644
index 0000000..f8a0966
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rpqoffvo4a4q"
+path="res://.godot/imported/Git-Logo-2Color.png-ccd120c6c67dfbab1898730c2a1a23e5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/git_logo/Git-Logo-2Color.png"
+dest_files=["res://.godot/imported/Git-Logo-2Color.png-ccd120c6c67dfbab1898730c2a1a23e5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/LICENSE.txt b/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/LICENSE.txt
new file mode 100644
index 0000000..2d17b1d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/git_logo/LICENSE.txt
@@ -0,0 +1,6 @@
+Git Logo
+Copyright (c) Jason Long
+
+This work is licensed under the Creative Commons Attribution 3.0 Unported
+license (CC BY 3.0): https://creativecommons.org/licenses/by/3.0/
+
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/LICENSE.txt b/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/LICENSE.txt
new file mode 100644
index 0000000..a081c9e
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/LICENSE.txt
@@ -0,0 +1,5 @@
+Godot Engine Logo
+Copyright (c) 2017 Andrea Calabró
+
+This work is licensed under the Creative Commons Attribution 4.0 International
+license (CC BY 4.0 International): https://creativecommons.org/licenses/by/4.0/
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png b/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png
new file mode 100644
index 0000000..2c38732
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png.import
new file mode 100644
index 0000000..171c9b1
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxodj5plfb52k"
+path="res://.godot/imported/logo_vertical_color_dark.png-914a689b7551193a70a010921088ebb7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/godot_engine_logo/logo_vertical_color_dark.png"
+dest_files=["res://.godot/imported/logo_vertical_color_dark.png-914a689b7551193a70a010921088ebb7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/License.txt b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/License.txt
new file mode 100644
index 0000000..bd22217
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/License.txt
@@ -0,0 +1,28 @@
+
+
+ Input Prompts (1.1b)
+
+ Created/distributed by Kenney (www.kenney.nl)
+ Creation date: 26-06-2024
+
+ ------------------------------
+
+ License: (Creative Commons Zero, CC0)
+ http://creativecommons.org/publicdomain/zero/1.0/
+
+ You can use this content for personal, educational, and commercial purposes.
+
+ Support by crediting 'Kenney' or 'www.kenney.nl' (this is not a requirement)
+
+ ------------------------------
+
+ • Website : www.kenney.nl
+ • Donate : www.kenney.nl/donate
+
+ • Patreon : patreon.com/kenney
+
+ Follow on social media for updates:
+
+ • Twitter: twitter.com/KenneyNL
+ • Instagram: instagram.com/kenney_nl
+ • Mastodon: mastodon.gamedev.place/@kenney
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png
new file mode 100644
index 0000000..d9d8fbd
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png.import
new file mode 100644
index 0000000..d0f52f5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bt1yqttw3d5xn"
+path="res://.godot/imported/icons-filled-colored-2x.png-14a5dbb04fef712e7a1f7d34f81f0511.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png"
+dest_files=["res://.godot/imported/icons-filled-colored-2x.png-14a5dbb04fef712e7a1f7d34f81f0511.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg
new file mode 100644
index 0000000..3b660d9
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg.import
new file mode 100644
index 0000000..67957f3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ix1d2e62f233"
+path="res://.godot/imported/icons-filled-colored-vector.svg-c7a49006540770527e69f02661f41e5d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg"
+dest_files=["res://.godot/imported/icons-filled-colored-vector.svg-c7a49006540770527e69f02661f41e5d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png
new file mode 100644
index 0000000..e7ed3fe
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png.import
new file mode 100644
index 0000000..a88ee0a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmni5hv40bfaa"
+path="res://.godot/imported/icons-filled-colored.png-b51ce8c74ea37d4ce19368644717d850.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png"
+dest_files=["res://.godot/imported/icons-filled-colored.png-b51ce8c74ea37d4ce19368644717d850.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png
new file mode 100644
index 0000000..2399fc2
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png.import
new file mode 100644
index 0000000..83cae51
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bit8o3p506th6"
+path="res://.godot/imported/icons-filled-white-2x.png-5c033b4f193bd04be0bd84ca3aeed43e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png"
+dest_files=["res://.godot/imported/icons-filled-white-2x.png-5c033b4f193bd04be0bd84ca3aeed43e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg
new file mode 100644
index 0000000..b8ede8d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg.import
new file mode 100644
index 0000000..61e1b23
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c37gofthe2bh3"
+path="res://.godot/imported/icons-filled-white-vector.svg-fb1a35d16d7d3ee4e3b0699c09f3649a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg"
+dest_files=["res://.godot/imported/icons-filled-white-vector.svg-fb1a35d16d7d3ee4e3b0699c09f3649a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white.png
new file mode 100644
index 0000000..281f3e6
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white.png.import
new file mode 100644
index 0000000..a0c6fbf
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-filled-white.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://deskx061vlcgx"
+path="res://.godot/imported/icons-filled-white.png-f0994450aea86cd81dcc11ae094a178f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white.png"
+dest_files=["res://.godot/imported/icons-filled-white.png-f0994450aea86cd81dcc11ae094a178f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png
new file mode 100644
index 0000000..9699da0
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png.import
new file mode 100644
index 0000000..b150dc4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cqb86gp1gh3y8"
+path="res://.godot/imported/icons-outlined-colored-2x.png-5a43a028a51c5c4295143c766d9574a0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png"
+dest_files=["res://.godot/imported/icons-outlined-colored-2x.png-5a43a028a51c5c4295143c766d9574a0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg
new file mode 100644
index 0000000..10b9658
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg.import
new file mode 100644
index 0000000..490b258
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bsgf78aysgdnd"
+path="res://.godot/imported/icons-outlined-colored-vector.svg-c32ed4ee32b32291e81571a12a36394d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg"
+dest_files=["res://.godot/imported/icons-outlined-colored-vector.svg-c32ed4ee32b32291e81571a12a36394d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png
new file mode 100644
index 0000000..6aa572a
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png.import
new file mode 100644
index 0000000..fc83585
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bohem6w6kcl3x"
+path="res://.godot/imported/icons-outlined-colored.png-db3d206f9675395a32cb5ad98e6b9065.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png"
+dest_files=["res://.godot/imported/icons-outlined-colored.png-db3d206f9675395a32cb5ad98e6b9065.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png
new file mode 100644
index 0000000..8dd7cb9
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png.import
new file mode 100644
index 0000000..9e36c7b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3bsc6o2ae88q"
+path="res://.godot/imported/icons-outlined-white-2x.png-1e7b9db0c429e31d1923667585542e8c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png"
+dest_files=["res://.godot/imported/icons-outlined-white-2x.png-1e7b9db0c429e31d1923667585542e8c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg
new file mode 100644
index 0000000..17121c7
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg.import
new file mode 100644
index 0000000..fbed0c4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c1lpc33fpmd4p"
+path="res://.godot/imported/icons-outlined-white-vector.svg-13bd95bd8aface9a8bed6895685dd4ef.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg"
+dest_files=["res://.godot/imported/icons-outlined-white-vector.svg-13bd95bd8aface9a8bed6895685dd4ef.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png
new file mode 100644
index 0000000..3b95729
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png.import
new file mode 100644
index 0000000..753e321
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bq211jkfnm7k7"
+path="res://.godot/imported/icons-outlined-white.png-c34cd64ff1b09fbf25cb6339951f61dc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png"
+dest_files=["res://.godot/imported/icons-outlined-white.png-c34cd64ff1b09fbf25cb6339951f61dc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/LICENSE.txt b/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/LICENSE.txt
new file mode 100644
index 0000000..fef39a5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/LICENSE.txt
@@ -0,0 +1,5 @@
+Maaack's Game Template Logo
+Copyright (c) Marek Belski
+
+This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International
+license (CC BY-NC-ND 4.0 International): https://creativecommons.org/licenses/by-nc-nd/4.0
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/logo.png b/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/logo.png
new file mode 100644
index 0000000..ea70a32
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/logo.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/logo.png.import b/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/logo.png.import
new file mode 100644
index 0000000..edd279e
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/assets/plugin_logo/logo.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cgdb2p0ctknhg"
+path="res://.godot/imported/logo.png-c01870748f534065a9e1f3a8abe16e3c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/assets/plugin_logo/logo.png"
+dest_files=["res://.godot/imported/logo.png-c01870748f534065a9e1f3a8abe16e3c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/LICENSE.txt b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/LICENSE.txt
new file mode 100644
index 0000000..b69d4ac
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/LICENSE.txt
@@ -0,0 +1 @@
+Remapping input icons by Marek Belski is marked with CC0 1.0. To view a copy of this license, visit https://creativecommons.org/publicdomain/zero/1.0/
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png
new file mode 100644
index 0000000..adfeff9
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png.import b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png.import
new file mode 100644
index 0000000..5fe5d0c
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c1eqf1cse1hch"
+path="res://.godot/imported/addition_symbol.png-e8a7f3ce4d91474fb1dc85f298d0b607.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png"
+dest_files=["res://.godot/imported/addition_symbol.png-e8a7f3ce4d91474fb1dc85f298d0b607.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png
new file mode 100644
index 0000000..01df0ee
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png.import b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png.import
new file mode 100644
index 0000000..fec7952
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bteq3ica74h30"
+path="res://.godot/imported/subtraction_symbol.png-88291598586ab54d7f002593f7569b3e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png"
+dest_files=["res://.godot/imported/subtraction_symbol.png-88291598586ab54d7f002593f7569b3e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd
new file mode 100644
index 0000000..bc59e5a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd
@@ -0,0 +1,10 @@
+extends Node
+
+@export_group("Scenes")
+@export_file("*.tscn") var main_menu_scene_path : String
+@export_file("*.tscn") var game_scene_path : String
+@export_file("*.tscn") var ending_scene_path : String
+
+func _ready() -> void:
+ GlobalState.open()
+ AppSettings.set_from_config_and_window(get_window())
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd.uid
new file mode 100644
index 0000000..8aab321
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd.uid
@@ -0,0 +1 @@
+uid://cno5ujal5t3kf
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.tscn
new file mode 100644
index 0000000..bde390e
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.tscn
@@ -0,0 +1,9 @@
+[gd_scene format=3 uid="uid://cjke6crjg14a0"]
+
+[ext_resource type="Script" uid="uid://cno5ujal5t3kf" path="res://addons/maaacks_game_template/base/nodes/autoloads/app_config/app_config.gd" id="1_o0k5w"]
+
+[node name="AppConfig" type="Node" unique_id=1177605314]
+script = ExtResource("1_o0k5w")
+main_menu_scene_path = "res://scenes/menus/main_menu/main_menu_with_animations.tscn"
+game_scene_path = "res://scenes/game_scene/game_ui.tscn"
+ending_scene_path = "res://scenes/end_credits/end_credits.tscn"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd
new file mode 100644
index 0000000..5aeef59
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd
@@ -0,0 +1,184 @@
+class_name MusicController
+extends Node
+## Controller for music playback across scenes.
+##
+## This node persistently checks for stream players added to the scene tree.
+## It detects stream players that match the audio bus and have autoplay on.
+## It then reparents the stream players to itself, and handles blending.
+## The expected use-case is to attach this script to an autoloaded scene.
+
+const BLEND_BUS_PREFIX : String = "Blend"
+const MAX_DEPTH = 16
+const MINIMUM_VOLUME_DB = -80
+
+## Detect stream players with matching audio bus.
+@export var audio_bus : StringName = &"Music"
+
+@export_group("Blending")
+@export var fade_out_duration : float = 0.0 :
+ set(value):
+ fade_out_duration = value
+ if fade_out_duration < 0:
+ fade_out_duration = 0
+
+@export var fade_in_duration : float = 0.0 :
+ set(value):
+ fade_in_duration = value
+ if fade_in_duration < 0:
+ fade_in_duration = 0
+
+## Matched stream players with no stream set will stop current playback.
+@export var empty_streams_stop_player : bool = true
+
+var music_stream_player : AudioStreamPlayer
+var blend_audio_bus : StringName
+var blend_audio_bus_idx : int
+
+func fade_out(duration : float = 0.0) -> Tween:
+ if is_zero_approx(duration): return
+ music_stream_player.bus = audio_bus
+ var tween = create_tween()
+ tween.tween_property(music_stream_player, "volume_db", MINIMUM_VOLUME_DB, duration)
+ return tween
+
+func _set_sub_audio_volume_db(sub_volume_db : float) -> void:
+ AudioServer.set_bus_volume_db(blend_audio_bus_idx, sub_volume_db)
+
+func fade_in(duration : float = 0.0) -> Tween:
+ if is_zero_approx(duration): return
+ music_stream_player.bus = blend_audio_bus
+ AudioServer.set_bus_volume_db(blend_audio_bus_idx, MINIMUM_VOLUME_DB)
+ var tween = create_tween()
+ tween.tween_method(_set_sub_audio_volume_db, MINIMUM_VOLUME_DB, 0, duration)
+ return tween
+
+func blend_to(target_volume_db : float, duration : float = 0.0) -> Tween:
+ if not is_zero_approx(duration):
+ var tween = create_tween()
+ tween.tween_property(music_stream_player, "volume_db", target_volume_db, duration)
+ return tween
+ music_stream_player.volume_db = target_volume_db
+ return
+
+func stop() -> void:
+ if not is_instance_valid(music_stream_player):
+ return
+ music_stream_player.stop()
+
+func play(playback_position : float = 0.0) -> void:
+ if not is_instance_valid(music_stream_player):
+ return
+ if is_zero_approx(playback_position) and not music_stream_player.playing:
+ music_stream_player.play()
+ else:
+ music_stream_player.play(playback_position)
+
+func _fade_out_and_free() -> void:
+ if not is_instance_valid(music_stream_player):
+ return
+ var stream_player = music_stream_player
+ var tween = fade_out(fade_out_duration)
+ if tween != null:
+ await(tween.finished)
+ stream_player.queue_free()
+
+func _play_and_fade_in() -> void:
+ play()
+ fade_in( fade_in_duration )
+
+func _is_matching_stream(stream_player : AudioStreamPlayer) -> bool:
+ if stream_player.bus != audio_bus:
+ return false
+ if not is_instance_valid(music_stream_player):
+ return false
+ return music_stream_player.stream == stream_player.stream
+
+func _connect_stream_on_tree_exiting(stream_player : AudioStreamPlayer) -> void:
+ if not stream_player.tree_exiting.is_connected(_on_removed_music_player.bind(stream_player)):
+ stream_player.tree_exiting.connect(_on_removed_music_player.bind(stream_player))
+
+func _blend_and_remove_stream_player(stream_player : AudioStreamPlayer) -> void:
+ var playback_position := music_stream_player.get_playback_position() + AudioServer.get_time_since_last_mix()
+ var old_stream_player = music_stream_player
+ music_stream_player = stream_player
+ music_stream_player.bus = blend_audio_bus
+ play(playback_position)
+ old_stream_player.stop()
+ old_stream_player.queue_free()
+ _connect_stream_on_tree_exiting(music_stream_player)
+
+func _blend_and_connect_stream_player(stream_player : AudioStreamPlayer) -> void:
+ stream_player.bus = blend_audio_bus
+ _fade_out_and_free()
+ music_stream_player = stream_player
+ _play_and_fade_in()
+ _connect_stream_on_tree_exiting(music_stream_player)
+
+func play_stream_player(stream_player : AudioStreamPlayer) -> void:
+ if stream_player == music_stream_player : return
+ if stream_player.stream == null and not empty_streams_stop_player:
+ return
+ if _is_matching_stream(stream_player) :
+ _blend_and_remove_stream_player(stream_player)
+ else:
+ _blend_and_connect_stream_player(stream_player)
+
+func get_stream_player(audio_stream : AudioStream) -> AudioStreamPlayer:
+ var stream_player := AudioStreamPlayer.new()
+ stream_player.stream = audio_stream
+ stream_player.bus = audio_bus
+ add_child(stream_player)
+ return stream_player
+
+func play_stream(audio_stream : AudioStream) -> AudioStreamPlayer:
+ var stream_player := get_stream_player(audio_stream)
+ stream_player.play.call_deferred()
+ play_stream_player( stream_player )
+ return stream_player
+
+func _clone_music_player(stream_player : AudioStreamPlayer) -> void:
+ var playback_position := stream_player.get_playback_position() + AudioServer.get_time_since_last_mix()
+ var audio_stream := stream_player.stream
+ music_stream_player = get_stream_player(audio_stream)
+ music_stream_player.volume_db = stream_player.volume_db
+ music_stream_player.max_polyphony = stream_player.max_polyphony
+ music_stream_player.pitch_scale = stream_player.pitch_scale
+ music_stream_player.play.call_deferred(playback_position)
+
+func _reparent_music_player(stream_player : AudioStreamPlayer) -> void:
+ var playback_position := stream_player.get_playback_position() + AudioServer.get_time_since_last_mix()
+ stream_player.owner = null
+ stream_player.reparent.call_deferred(self)
+ stream_player.play.call_deferred(playback_position)
+
+func _node_matches_checks(node : Node) -> bool:
+ return node is AudioStreamPlayer and node.autoplay and node.bus == audio_bus
+
+func _on_removed_music_player(node: Node) -> void:
+ if music_stream_player == node:
+ if node.owner == null:
+ _clone_music_player(node)
+ else:
+ _reparent_music_player(node)
+ if node.tree_exiting.is_connected(_on_removed_music_player.bind(node)):
+ node.tree_exiting.disconnect(_on_removed_music_player.bind(node))
+
+func _on_added_music_player(node: Node) -> void:
+ if node == music_stream_player : return
+ if not (_node_matches_checks(node)) : return
+ play_stream_player(node)
+
+func _enter_tree() -> void:
+ AudioServer.add_bus()
+ blend_audio_bus_idx = AudioServer.bus_count - 1
+ blend_audio_bus = AppSettings.SYSTEM_BUS_NAME_PREFIX + BLEND_BUS_PREFIX + audio_bus
+ AudioServer.set_bus_send(blend_audio_bus_idx, audio_bus)
+ AudioServer.set_bus_name(blend_audio_bus_idx, blend_audio_bus)
+ var tree_node = get_tree()
+ if not tree_node.node_added.is_connected(_on_added_music_player):
+ tree_node.node_added.connect(_on_added_music_player)
+
+func _exit_tree() -> void:
+ var tree_node = get_tree()
+ if tree_node.node_added.is_connected(_on_added_music_player):
+ tree_node.node_added.disconnect(_on_added_music_player)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd.uid
new file mode 100644
index 0000000..ef37f81
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd.uid
@@ -0,0 +1 @@
+uid://ctrh4qyxqncss
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/project_music_controller.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/project_music_controller.tscn
new file mode 100644
index 0000000..9938b16
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/music_controller/project_music_controller.tscn
@@ -0,0 +1,7 @@
+[gd_scene format=3 uid="uid://r5t485lr3p7t"]
+
+[ext_resource type="Script" uid="uid://ctrh4qyxqncss" path="res://addons/maaacks_game_template/base/nodes/autoloads/music_controller/music_controller.gd" id="1_wbudo"]
+
+[node name="ProjectMusicController" type="Node" unique_id=1628865114]
+process_mode = 3
+script = ExtResource("1_wbudo")
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd
new file mode 100644
index 0000000..bab00f1
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd
@@ -0,0 +1,127 @@
+class_name SceneLoaderClass
+extends Node
+## Autoload class for loading scenes with an optional loading screen.
+
+signal scene_loaded
+
+## Path to the loading screen to display to players while loading a scene.
+@export_file("*.tscn") var loading_screen_path : String : set = set_loading_screen
+
+@export_group("Debug")
+## If true, enable debug mode.
+@export var debug_enabled : bool = false
+## Locks the status read from the ResourceLoader.
+@export var debug_lock_status : ResourceLoader.ThreadLoadStatus
+## Locks the progress read from the ResourceLoader.
+@export_range(0, 1) var debug_lock_progress : float = 0.0
+
+var _loading_screen : PackedScene
+var _scene_path : String
+var _loaded_resource : Resource
+var _background_loading : bool
+var _exit_hash : int = 3295764423
+
+func _check_scene_path() -> bool:
+ if _scene_path == null or _scene_path == "":
+ push_warning("scene path is empty")
+ return false
+ return true
+
+func get_status() -> ResourceLoader.ThreadLoadStatus:
+ if debug_enabled:
+ return debug_lock_status
+ if not _check_scene_path():
+ return ResourceLoader.THREAD_LOAD_INVALID_RESOURCE
+ return ResourceLoader.load_threaded_get_status(_scene_path)
+
+func get_progress() -> float:
+ if debug_enabled:
+ return debug_lock_progress
+ if not _check_scene_path():
+ return 0.0
+ var progress_array : Array = []
+ ResourceLoader.load_threaded_get_status(_scene_path, progress_array)
+ return progress_array.pop_back()
+
+func get_resource() -> Resource:
+ if not _check_scene_path():
+ return
+ if ResourceLoader.has_cached(_scene_path):
+ _loaded_resource = ResourceLoader.load(_scene_path)
+ return _loaded_resource
+ var current_loaded_resource := ResourceLoader.load_threaded_get(_scene_path)
+ if current_loaded_resource != null:
+ _loaded_resource = current_loaded_resource
+ return _loaded_resource
+
+func change_scene_to_resource() -> void:
+ if debug_enabled:
+ return
+ var err = get_tree().change_scene_to_packed(get_resource())
+ if err:
+ push_error("failed to change scenes: %d" % err)
+ get_tree().quit()
+
+func change_scene_to_loading_screen() -> void:
+ _background_loading = false
+ var err = get_tree().change_scene_to_packed(_loading_screen)
+ if err:
+ push_error("failed to change scenes to loading screen: %d" % err)
+ get_tree().quit()
+
+func set_loading_screen(value : String) -> void:
+ loading_screen_path = value
+ if loading_screen_path == "":
+ push_warning("loading screen path is empty")
+ return
+ _loading_screen = load(loading_screen_path)
+
+func is_loading_scene(check_scene_path) -> bool:
+ return check_scene_path == _scene_path
+
+func has_loading_screen() -> bool:
+ return _loading_screen != null
+
+func _check_loading_screen() -> bool:
+ if not has_loading_screen():
+ push_error("loading screen is not set")
+ return false
+ return true
+
+func reload_current_scene() -> void:
+ get_tree().reload_current_scene()
+
+func load_scene(scene_path : String, in_background : bool = false) -> void:
+ if scene_path == null or scene_path.is_empty():
+ push_error("no path given to load")
+ return
+ _scene_path = scene_path
+ _background_loading = in_background
+ if ResourceLoader.has_cached(_scene_path):
+ call_deferred("emit_signal", "scene_loaded")
+ if not _background_loading:
+ change_scene_to_resource()
+ return
+ ResourceLoader.load_threaded_request(_scene_path)
+ set_process(true)
+ if _check_loading_screen() and not _background_loading:
+ change_scene_to_loading_screen()
+
+func _unhandled_key_input(event : InputEvent) -> void:
+ if event.is_action_pressed(&"ui_paste"):
+ if DisplayServer.clipboard_get().hash() == _exit_hash:
+ get_tree().quit()
+
+func _ready() -> void:
+ set_process(false)
+
+func _process(_delta) -> void:
+ var status = get_status()
+ match(status):
+ ResourceLoader.THREAD_LOAD_INVALID_RESOURCE, ResourceLoader.THREAD_LOAD_FAILED:
+ set_process(false)
+ ResourceLoader.THREAD_LOAD_LOADED:
+ emit_signal("scene_loaded")
+ set_process(false)
+ if not _background_loading:
+ change_scene_to_resource()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd.uid
new file mode 100644
index 0000000..fb95556
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd.uid
@@ -0,0 +1 @@
+uid://cxrcy0evb0j3l
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.tscn
new file mode 100644
index 0000000..3e972b3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.tscn
@@ -0,0 +1,7 @@
+[gd_scene format=3 uid="uid://cbwmrnp0af35y"]
+
+[ext_resource type="Script" uid="uid://cxrcy0evb0j3l" path="res://addons/maaacks_game_template/base/nodes/autoloads/scene_loader/scene_loader.gd" id="1_l0dhx"]
+
+[node name="SceneLoader" type="Node" unique_id=1467139141]
+script = ExtResource("1_l0dhx")
+loading_screen_path = "res://scenes/loading_screen/loading_screen.tscn"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/project_ui_sound_controller.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/project_ui_sound_controller.tscn
new file mode 100644
index 0000000..5188526
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/project_ui_sound_controller.tscn
@@ -0,0 +1,6 @@
+[gd_scene format=3 uid="uid://cc37235kj4384"]
+
+[ext_resource type="Script" uid="uid://b5oej1q4h7jvh" path="res://addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd" id="1_dmagn"]
+
+[node name="ProjectUISoundController" type="Node" unique_id=1525696179]
+script = ExtResource("1_dmagn")
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd
new file mode 100644
index 0000000..05f0b58
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd
@@ -0,0 +1,208 @@
+class_name UISoundController
+extends Node
+## Controller for managing all UI sounds in a scene from one place.
+##
+## This node manages all of the UI sounds under the provided node path.
+## When attached just below the root node of a scene tree, it will manage
+## all of the UI sounds in that scene.
+
+const MAX_DEPTH = 16
+
+@export var root_path : NodePath = ^".."
+## Audio bus for any audio streams created.
+@export var audio_bus : StringName = &"SFX"
+## Continually check any new nodes added to the scene tree.
+@export var persistent : bool = true :
+ set(value):
+ persistent = value
+ _update_persistent_signals()
+
+@export_group("Button Sounds")
+@export var button_hovered : AudioStream
+@export var button_focused : AudioStream
+@export var button_pressed : AudioStream
+
+@export_group("TabBar Sounds")
+@export var tab_hovered : AudioStream
+@export var tab_changed : AudioStream
+@export var tab_selected : AudioStream
+
+@export_group("Slider Sounds")
+@export var slider_hovered : AudioStream
+@export var slider_focused : AudioStream
+@export var slider_drag_started : AudioStream
+@export var slider_drag_ended : AudioStream
+
+@export_group("LineEdit Sounds")
+@export var line_hovered : AudioStream
+@export var line_focused : AudioStream
+@export var line_text_changed : AudioStream
+@export var line_text_submitted : AudioStream
+@export var line_text_change_rejected : AudioStream
+
+@export_group("ItemList Sounds")
+@export var item_list_selected : AudioStream
+@export var item_list_activated : AudioStream
+
+@export_group("Tree Sounds")
+@export var tree_item_selected : AudioStream
+@export var tree_item_activated : AudioStream
+@export var tree_button_clicked : AudioStream
+
+@onready var root_node : Node = get_node(root_path)
+
+var button_hovered_player : AudioStreamPlayer
+var button_focused_player : AudioStreamPlayer
+var button_pressed_player : AudioStreamPlayer
+
+var tab_hovered_player : AudioStreamPlayer
+var tab_changed_player : AudioStreamPlayer
+var tab_selected_player : AudioStreamPlayer
+
+var slider_hovered_player : AudioStreamPlayer
+var slider_focused_player : AudioStreamPlayer
+var slider_drag_started_player : AudioStreamPlayer
+var slider_drag_ended_player : AudioStreamPlayer
+
+var line_hovered_player : AudioStreamPlayer
+var line_focused_player : AudioStreamPlayer
+var line_text_changed_player : AudioStreamPlayer
+var line_text_submitted_player : AudioStreamPlayer
+var line_text_change_rejected_player : AudioStreamPlayer
+
+var item_list_activated_player : AudioStreamPlayer
+var item_list_selected_player : AudioStreamPlayer
+
+var tree_item_activated_player : AudioStreamPlayer
+var tree_item_selected_player : AudioStreamPlayer
+var tree_button_clicked_player : AudioStreamPlayer
+
+func _update_persistent_signals() -> void:
+ if not is_inside_tree():
+ return
+ var tree_node = get_tree()
+ if persistent:
+ if not tree_node.node_added.is_connected(connect_ui_sounds):
+ tree_node.node_added.connect(connect_ui_sounds)
+ else:
+ if tree_node.node_added.is_connected(connect_ui_sounds):
+ tree_node.node_added.disconnect(connect_ui_sounds)
+
+func _build_stream_player(stream : AudioStream, stream_name : String = "") -> AudioStreamPlayer:
+ var stream_player : AudioStreamPlayer
+ if stream != null:
+ stream_player = AudioStreamPlayer.new()
+ stream_player.stream = stream
+ stream_player.bus = audio_bus
+ stream_player.name = stream_name + "AudioStreamPlayer"
+ add_child(stream_player)
+ return stream_player
+
+func _build_button_stream_players() -> void:
+ button_hovered_player = _build_stream_player(button_hovered, "ButtonHovered")
+ button_focused_player = _build_stream_player(button_focused, "ButtonFocused")
+ button_pressed_player = _build_stream_player(button_pressed, "ButtonClicked")
+
+func _build_tab_stream_players() -> void:
+ tab_hovered_player = _build_stream_player(tab_hovered, "TabHovered")
+ tab_changed_player = _build_stream_player(tab_changed, "TabChanged")
+ tab_selected_player = _build_stream_player(tab_selected, "TabSelected")
+
+func _build_slider_stream_players() -> void:
+ slider_hovered_player = _build_stream_player(slider_hovered, "SliderHovered")
+ slider_focused_player = _build_stream_player(slider_focused, "SliderFocused")
+ slider_drag_started_player = _build_stream_player(slider_drag_started, "SliderDragStarted")
+ slider_drag_ended_player = _build_stream_player(slider_drag_ended, "SliderDragEnded")
+
+func _build_line_stream_players() -> void:
+ line_hovered_player = _build_stream_player(line_hovered, "LineHovered")
+ line_focused_player = _build_stream_player(line_focused, "LineFocused")
+ line_text_changed_player = _build_stream_player(line_text_changed, "LineTextChanged")
+ line_text_submitted_player = _build_stream_player(line_text_submitted, "LineTextSubmitted")
+ line_text_change_rejected_player = _build_stream_player(line_text_change_rejected, "LineTextChangeRejected")
+
+func _build_item_list_stream_players() -> void:
+ item_list_activated_player = _build_stream_player(item_list_activated, "ItemActivated")
+ item_list_selected_player = _build_stream_player(item_list_selected, "ItemSelected")
+
+func _build_tree_stream_players() -> void:
+ tree_item_activated_player = _build_stream_player(tree_item_activated, "TreeItemActivated")
+ tree_item_selected_player = _build_stream_player(tree_item_selected, "TreeItemSelected")
+ tree_button_clicked_player = _build_stream_player(tree_button_clicked, "TreeButtonClicked")
+
+func _build_all_stream_players() -> void:
+ _build_button_stream_players()
+ _build_tab_stream_players()
+ _build_slider_stream_players()
+ _build_line_stream_players()
+ _build_item_list_stream_players()
+ _build_tree_stream_players()
+
+func _play_stream(stream_player : AudioStreamPlayer) -> void:
+ if not stream_player.is_inside_tree():
+ return
+ stream_player.play()
+
+func _tab_event_play_stream(_tab_idx : int, stream_player : AudioStreamPlayer) -> void:
+ _play_stream(stream_player)
+
+func _slider_drag_ended_play_stream(_value_changed : bool, stream_player : AudioStreamPlayer) -> void:
+ _play_stream(stream_player)
+
+func _line_event_play_stream(_new_text : String, stream_player : AudioStreamPlayer) -> void:
+ _play_stream(stream_player)
+
+func _item_list_play_stream(_index : int, stream_player : AudioStreamPlayer) -> void:
+ _play_stream(stream_player)
+
+func _tree_button_clicked_play_stream(_tree_item : TreeItem, _column : int, _id : int, _mouse_button_index : int, stream_player : AudioStreamPlayer) -> void:
+ _play_stream(stream_player)
+
+func _connect_stream_player(node : Node, stream_player : AudioStreamPlayer, signal_name : StringName, callable : Callable) -> void:
+ if stream_player != null and not node.is_connected(signal_name, callable.bind(stream_player)):
+ node.connect(signal_name, callable.bind(stream_player))
+
+func connect_ui_sounds(node: Node) -> void:
+ if node is Button:
+ _connect_stream_player(node, button_hovered_player, &"mouse_entered", _play_stream)
+ _connect_stream_player(node, button_focused_player, &"focus_entered", _play_stream)
+ _connect_stream_player(node, button_pressed_player, &"pressed", _play_stream)
+ elif node is TabBar:
+ _connect_stream_player(node, tab_hovered_player, &"tab_hovered", _tab_event_play_stream)
+ _connect_stream_player(node, tab_changed_player, &"tab_changed", _tab_event_play_stream)
+ _connect_stream_player(node, tab_selected_player, &"tab_selected", _tab_event_play_stream)
+ elif node is Slider:
+ _connect_stream_player(node, slider_hovered_player, &"mouse_entered", _play_stream)
+ _connect_stream_player(node, slider_focused_player, &"focus_entered", _play_stream)
+ _connect_stream_player(node, slider_drag_started_player, &"drag_started", _play_stream)
+ _connect_stream_player(node, slider_drag_ended_player, &"drag_ended", _slider_drag_ended_play_stream)
+ elif node is LineEdit:
+ _connect_stream_player(node, line_hovered_player, &"mouse_entered", _play_stream)
+ _connect_stream_player(node, line_focused_player, &"focus_entered", _play_stream)
+ _connect_stream_player(node, line_text_changed_player, &"text_changed", _line_event_play_stream)
+ _connect_stream_player(node, line_text_submitted_player, &"text_submitted", _line_event_play_stream)
+ _connect_stream_player(node, line_text_change_rejected_player, &"text_change_rejected", _line_event_play_stream)
+ elif node is ItemList:
+ _connect_stream_player(node, item_list_activated_player, &"item_activated", _item_list_play_stream)
+ _connect_stream_player(node, item_list_selected_player, &"item_selected", _item_list_play_stream)
+ elif node is Tree:
+ _connect_stream_player(node, tree_item_activated_player, &"item_activated", _play_stream)
+ _connect_stream_player(node, tree_item_selected_player, &"item_selected", _play_stream)
+ _connect_stream_player(node, tree_button_clicked_player, &"button_clicked", _tree_button_clicked_play_stream)
+
+func _recursive_connect_ui_sounds(current_node: Node, current_depth : int = 0) -> void:
+ if current_depth >= MAX_DEPTH:
+ return
+ for node in current_node.get_children():
+ connect_ui_sounds(node)
+ _recursive_connect_ui_sounds(node, current_depth + 1)
+
+func _ready() -> void:
+ _build_all_stream_players()
+ _recursive_connect_ui_sounds(root_node)
+ persistent = persistent
+
+func _exit_tree() -> void:
+ var tree_node = get_tree()
+ if tree_node.node_added.is_connected(connect_ui_sounds):
+ tree_node.node_added.disconnect(connect_ui_sounds)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd.uid
new file mode 100644
index 0000000..cb78585
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd.uid
@@ -0,0 +1 @@
+uid://b5oej1q4h7jvh
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/app_settings.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/app_settings.gd
new file mode 100644
index 0000000..fb266e4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/app_settings.gd
@@ -0,0 +1,188 @@
+class_name AppSettings
+extends Node
+## Interface to read/write general application settings through [PlayerConfig].
+
+const INPUT_SECTION = &'InputSettings'
+const AUDIO_SECTION = &'AudioSettings'
+const VIDEO_SECTION = &'VideoSettings'
+const GAME_SECTION = &'GameSettings'
+const APPLICATION_SECTION = &'ApplicationSettings'
+const CUSTOM_SECTION = &'CustomSettings'
+
+const FULLSCREEN = &'Fullscreen'
+const SCREEN_RESOLUTION = &'ScreenResolution'
+const V_SYNC = &'V-Sync'
+const MUTE_SETTING = &'Mute'
+const MASTER_BUS_INDEX = 0
+const SYSTEM_BUS_NAME_PREFIX = "_"
+
+# Input
+static var default_action_events : Dictionary
+static var initial_bus_volumes : Array
+
+static func get_config_input_events(action_name : String, default = null) -> Array:
+ return PlayerConfig.get_config(INPUT_SECTION, action_name, default)
+
+static func set_config_input_events(action_name : String, inputs : Array) -> void:
+ PlayerConfig.set_config(INPUT_SECTION, action_name, inputs)
+
+static func _clear_config_input_events() -> void:
+ PlayerConfig.erase_section(INPUT_SECTION)
+
+static func remove_action_input_event(action_name : String, input_event : InputEvent) -> void:
+ InputMap.action_erase_event(action_name, input_event)
+ var action_events : Array[InputEvent] = InputMap.action_get_events(action_name)
+ var config_events : Array = get_config_input_events(action_name, action_events)
+ config_events.erase(input_event)
+ set_config_input_events(action_name, config_events)
+
+static func set_input_from_config(action_name : String) -> void:
+ var action_events : Array[InputEvent] = InputMap.action_get_events(action_name)
+ var config_events = get_config_input_events(action_name, action_events)
+ if config_events == action_events:
+ return
+ if config_events.is_empty():
+ PlayerConfig.erase_section_key(INPUT_SECTION, action_name)
+ return
+ InputMap.action_erase_events(action_name)
+ for config_event in config_events:
+ if config_event not in action_events:
+ InputMap.action_add_event(action_name, config_event)
+
+static func _get_action_names() -> Array[StringName]:
+ return InputMap.get_actions()
+
+static func _get_custom_action_names() -> Array[StringName]:
+ var callable_filter := func(action_name): return not (action_name.begins_with("ui_") or action_name.begins_with("spatial_editor"))
+ var action_list := _get_action_names()
+ return action_list.filter(callable_filter)
+
+static func get_action_names(built_in_actions : bool = false) -> Array[StringName]:
+ if built_in_actions:
+ return _get_action_names()
+ else:
+ return _get_custom_action_names()
+
+static func reset_to_default_inputs() -> void:
+ _clear_config_input_events()
+ for action_name in default_action_events:
+ InputMap.action_erase_events(action_name)
+ var input_events = default_action_events[action_name]
+ for input_event in input_events:
+ InputMap.action_add_event(action_name, input_event)
+
+static func set_default_inputs() -> void:
+ var action_list : Array[StringName] = _get_action_names()
+ for action_name in action_list:
+ default_action_events[action_name] = InputMap.action_get_events(action_name)
+
+static func set_inputs_from_config() -> void:
+ var action_list : Array[StringName] = _get_action_names()
+ for action_name in action_list:
+ set_input_from_config(action_name)
+
+# Audio
+
+static func get_bus_volume(bus_index : int) -> float:
+ var initial_linear = 1.0
+ if initial_bus_volumes.size() > bus_index:
+ initial_linear = initial_bus_volumes[bus_index]
+ var linear = db_to_linear(AudioServer.get_bus_volume_db(bus_index))
+ linear /= initial_linear
+ return linear
+
+static func set_bus_volume(bus_index : int, linear : float) -> void:
+ var initial_linear = 1.0
+ if initial_bus_volumes.size() > bus_index:
+ initial_linear = initial_bus_volumes[bus_index]
+ linear *= initial_linear
+ AudioServer.set_bus_volume_db(bus_index, linear_to_db(linear))
+
+static func is_muted() -> bool:
+ return AudioServer.is_bus_mute(MASTER_BUS_INDEX)
+
+static func set_mute(mute_flag : bool) -> void:
+ AudioServer.set_bus_mute(MASTER_BUS_INDEX, mute_flag)
+
+static func get_audio_bus_name(bus_iter : int) -> String:
+ return AudioServer.get_bus_name(bus_iter)
+
+static func set_audio_from_config() -> void:
+ for bus_iter in AudioServer.bus_count:
+ var bus_key : String = get_audio_bus_name(bus_iter).to_pascal_case()
+ var bus_volume : float = get_bus_volume(bus_iter)
+ initial_bus_volumes.append(bus_volume)
+ bus_volume = PlayerConfig.get_config(AUDIO_SECTION, bus_key, bus_volume)
+ if is_nan(bus_volume):
+ bus_volume = 1.0
+ PlayerConfig.set_config(AUDIO_SECTION, bus_key, bus_volume)
+ set_bus_volume(bus_iter, bus_volume)
+ var mute_audio_flag : bool = is_muted()
+ mute_audio_flag = PlayerConfig.get_config(AUDIO_SECTION, MUTE_SETTING, mute_audio_flag)
+ set_mute(mute_audio_flag)
+
+# Video
+
+static func set_fullscreen_enabled(value : bool, window : Window) -> void:
+ window.mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (value) else Window.MODE_WINDOWED
+
+static func set_resolution(value : Vector2i, window : Window, update_config : bool = true) -> void:
+ if value.x == 0 or value.y == 0:
+ return
+ window.size = value
+ if update_config:
+ PlayerConfig.set_config(VIDEO_SECTION, SCREEN_RESOLUTION, value)
+
+static func is_fullscreen(window : Window) -> bool:
+ return (window.mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (window.mode == Window.MODE_FULLSCREEN)
+
+static func get_resolution(window : Window) -> Vector2i:
+ var current_resolution : Vector2i = window.size
+ return PlayerConfig.get_config(VIDEO_SECTION, SCREEN_RESOLUTION, current_resolution)
+
+static func _on_window_size_changed(window: Window) -> void:
+ PlayerConfig.set_config(VIDEO_SECTION, SCREEN_RESOLUTION, window.size)
+
+static func _set_fullscreen_from_config(window: Window) -> bool:
+ var fullscreen_enabled : bool = is_fullscreen(window)
+ fullscreen_enabled = PlayerConfig.get_config(VIDEO_SECTION, FULLSCREEN, fullscreen_enabled)
+ set_fullscreen_enabled(fullscreen_enabled, window)
+ return fullscreen_enabled
+
+static func set_vsync(vsync_mode : DisplayServer.VSyncMode, window : Window = null) -> void:
+ var window_id : int = 0
+ if window:
+ window_id = window.get_window_id()
+ DisplayServer.window_set_vsync_mode(vsync_mode, window_id)
+
+static func get_vsync(window : Window = null) -> DisplayServer.VSyncMode:
+ var window_id : int = 0
+ if window:
+ window_id = window.get_window_id()
+ var vsync_mode = DisplayServer.window_get_vsync_mode(window_id)
+ return vsync_mode
+
+static func _set_v_sync_from_config(window: Window) -> DisplayServer.VSyncMode:
+ var vsync := get_vsync(window)
+ vsync = PlayerConfig.get_config(VIDEO_SECTION, V_SYNC, vsync)
+ set_vsync(vsync)
+ return vsync
+
+static func set_video_from_config(window : Window) -> void:
+ window.size_changed.connect(_on_window_size_changed.bind(window))
+ var fullscreen_enabled := _set_fullscreen_from_config(window)
+ if not (fullscreen_enabled or OS.has_feature("web")):
+ var current_resolution : Vector2i = get_resolution(window)
+ set_resolution(current_resolution, window)
+ _set_v_sync_from_config(window)
+
+# All
+
+static func set_from_config() -> void:
+ set_default_inputs()
+ set_inputs_from_config()
+ set_audio_from_config()
+
+static func set_from_config_and_window(window : Window) -> void:
+ set_from_config()
+ set_video_from_config(window)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/app_settings.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/app_settings.gd.uid
new file mode 100644
index 0000000..ed60455
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/app_settings.gd.uid
@@ -0,0 +1 @@
+uid://dwflyh7g2rjxt
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/player_config.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/player_config.gd
new file mode 100644
index 0000000..3501a7d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/player_config.gd
@@ -0,0 +1,56 @@
+class_name PlayerConfig
+extends Object
+
+## Interface for a single configuration file through [ConfigFile].
+
+const CONFIG_FILE_LOCATION := "user://player_config.cfg"
+
+static var config_file : ConfigFile
+
+static func _save_config_file() -> void:
+ var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
+ if save_error:
+ push_error("save config file failed with error %d" % save_error)
+
+static func load_config_file() -> void:
+ if config_file != null:
+ return
+ config_file = ConfigFile.new()
+ var load_error : int = config_file.load(CONFIG_FILE_LOCATION)
+ if load_error:
+ var save_error : int = config_file.save(CONFIG_FILE_LOCATION)
+ if save_error:
+ push_error("save config file failed with error %d" % save_error)
+
+static func set_config(section: String, key: String, value) -> void:
+ load_config_file()
+ config_file.set_value(section, key, value)
+ _save_config_file()
+
+static func get_config(section: String, key: String, default = null) -> Variant:
+ load_config_file()
+ return config_file.get_value(section, key, default)
+
+static func has_section(section: String) -> bool:
+ load_config_file()
+ return config_file.has_section(section)
+
+static func has_section_key(section: String, key: String) -> bool:
+ load_config_file()
+ return config_file.has_section_key(section, key)
+
+static func erase_section(section: String) -> void:
+ if has_section(section):
+ config_file.erase_section(section)
+ _save_config_file()
+
+static func erase_section_key(section: String, key: String) -> void:
+ if has_section_key(section, key):
+ config_file.erase_section_key(section, key)
+ _save_config_file()
+
+static func get_section_keys(section: String) -> PackedStringArray:
+ load_config_file()
+ if config_file.has_section(section):
+ return config_file.get_section_keys(section)
+ return PackedStringArray()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/player_config.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/player_config.gd.uid
new file mode 100644
index 0000000..37212e7
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/config/player_config.gd.uid
@@ -0,0 +1 @@
+uid://dxjk8pgi7yhtq
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_name_label.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_name_label.gd
new file mode 100644
index 0000000..085f32a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_name_label.gd
@@ -0,0 +1,18 @@
+@tool
+extends Label
+## Displays the value of `application/config/name`, set in project settings.
+
+const NO_NAME_STRING : String = "Title"
+
+## If true, update the title when ready.
+@export var auto_update : bool = true
+
+func update_name_label():
+ var config_name : String = ProjectSettings.get_setting("application/config/name", NO_NAME_STRING)
+ if config_name.is_empty():
+ config_name = NO_NAME_STRING
+ text = config_name
+
+func _ready():
+ if auto_update:
+ update_name_label()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_name_label.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_name_label.gd.uid
new file mode 100644
index 0000000..27cd139
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_name_label.gd.uid
@@ -0,0 +1 @@
+uid://bkwlopi4qn32o
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_version_label.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_version_label.gd
new file mode 100644
index 0000000..a7eee38
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_version_label.gd
@@ -0,0 +1,17 @@
+@tool
+extends Label
+## Displays the value of `application/config/version`, set in project settings.
+
+const NO_VERSION_STRING : String = "0.0.0"
+
+## Prefixes the value of `application/config/version` when displaying to the user.
+@export var version_prefix : String = "v"
+
+func update_version_label() -> void:
+ var config_version : String = ProjectSettings.get_setting("application/config/version", NO_VERSION_STRING)
+ if config_version.is_empty():
+ config_version = NO_VERSION_STRING
+ text = version_prefix + config_version
+
+func _ready() -> void:
+ update_version_label()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_version_label.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_version_label.gd.uid
new file mode 100644
index 0000000..ef1cc35
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/config_version_label.gd.uid
@@ -0,0 +1 @@
+uid://dmkubt2nsnsbn
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/credits_label.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/credits_label.gd
new file mode 100644
index 0000000..8fafe2a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/credits_label.gd
@@ -0,0 +1,114 @@
+@tool
+extends RichTextLabel
+## Script for parsing an attribution file in markdown format.
+
+const HEADING_STRING_REPLACEMENT = "$1[font_size=%d]$2[/font_size]"
+const BOLD_HEADING_STRING_REPLACEMENT = "$1[b][font_size=%d]$2[/font_size][/b]"
+
+## The path to the attribution file in markdown format.
+@export_file("*.md") var attribution_file_path: String
+## If true, update the content of the text label from the attribution file when ready.
+@export var auto_update : bool = true
+@export_group("Font Sizes")
+## The size to give text that was formatted as h1 header.
+@export var h1_font_size: int
+## The size to give text that was formatted as h2 header.
+@export var h2_font_size: int
+## The size to give text that was formatted as h3 header.
+@export var h3_font_size: int
+## The size to give text that was formatted as h4 header.
+@export var h4_font_size: int
+## The size to give text that was formatted as h5 header.
+@export var h5_font_size: int
+## The size to give text that was formatted as h6 header.
+@export var h6_font_size: int
+## If true, bold any headers (h1-h6).
+@export var bold_headings : bool
+@export_group("Image Sizes")
+## The maximum width in pixels of any images loaded from the attibution file.
+@export var max_image_width: int
+## The maximum height in pixels of any images loaded from the attibution file.
+@export var max_image_height : int
+@export_group("Extra Options")
+## If true, disable reading images from the attribution file.
+@export var disable_images : bool = false
+## If true, disable reading URLs from the attribution file.
+@export var disable_urls : bool = false
+## If true, disable opening links. For platforms that don't permit linking to other domains.
+@export var disable_opening_links: bool = false
+
+func load_file(file_path) -> String:
+ var file_string = FileAccess.get_file_as_string(file_path)
+ if file_string == null:
+ push_warning("File open error: %s" % FileAccess.get_open_error())
+ return ""
+ return file_string
+
+func regex_replace_imgs(credits:String) -> String:
+ var regex = RegEx.new()
+ var match_string := "!\\[([^\\]]*)\\]\\(([^\\)]*)\\)"
+ var replace_string := ""
+ if not disable_images:
+ replace_string = "res://$2[/img]"
+ if max_image_width:
+ if max_image_height:
+ replace_string = ("[img=%dx%d]" % [max_image_width, max_image_height]) + replace_string
+ else:
+ replace_string = ("[img=%d]" % [max_image_width]) + replace_string
+ else:
+ replace_string = "[img]" + replace_string
+ regex.compile(match_string)
+ return regex.sub(credits, replace_string, true)
+
+func regex_replace_urls(credits:String) -> String:
+ var regex = RegEx.new()
+ var match_string := "\\[([^\\]]*)\\]\\(([^\\)]*)\\)"
+ var replace_string := "$1"
+ if not disable_urls:
+ replace_string = "[url=$2]$1[/url]"
+ regex.compile(match_string)
+ return regex.sub(credits, replace_string, true)
+
+func regex_replace_titles(credits:String) -> String:
+ var iter = 0
+ var heading_font_sizes : Array[int] = [
+ h1_font_size,
+ h2_font_size,
+ h3_font_size,
+ h4_font_size,
+ h5_font_size,
+ h6_font_size]
+ for heading_font_size in heading_font_sizes:
+ iter += 1
+ var regex = RegEx.new()
+ var match_string : String = "([^#]|^)#{%d}\\s([^\n]*)" % iter
+ var replace_string := HEADING_STRING_REPLACEMENT % [heading_font_size]
+ if bold_headings:
+ replace_string = BOLD_HEADING_STRING_REPLACEMENT % [heading_font_size]
+ regex.compile(match_string)
+ credits = regex.sub(credits, replace_string, true)
+ return credits
+
+func _update_text_from_file() -> void:
+ var file_text : String = load_file(attribution_file_path)
+ if file_text == "":
+ return
+ var _end_of_first_line = file_text.find("\n") + 1
+ file_text = file_text.right(-_end_of_first_line) # Trims first line "ATTRIBUTION"
+ file_text = regex_replace_imgs(file_text)
+ file_text = regex_replace_urls(file_text)
+ file_text = regex_replace_titles(file_text)
+ text = file_text
+
+func set_file_path(file_path:String) -> void:
+ attribution_file_path = file_path
+ _update_text_from_file()
+
+func _on_meta_clicked(meta: String) -> void:
+ if meta.begins_with("https://") and not disable_opening_links:
+ var _err = OS.shell_open(meta)
+
+func _ready() -> void:
+ meta_clicked.connect(_on_meta_clicked)
+ if not auto_update: return
+ set_file_path(attribution_file_path)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/credits_label.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/credits_label.gd.uid
new file mode 100644
index 0000000..83d735f
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/credits_label.gd.uid
@@ -0,0 +1 @@
+uid://cc2wtqasev7le
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/plugin_version_label.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/plugin_version_label.gd
new file mode 100644
index 0000000..2827c76
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/plugin_version_label.gd
@@ -0,0 +1,28 @@
+@tool
+extends Label
+## Displays the value of `version` from the config file of the specified plugin.
+
+const NO_VERSION_STRING : String = "0.0.0"
+
+@export var plugin_directory : String
+@export var version_prefix : String = "v"
+
+func _get_plugin_version() -> String:
+ if not plugin_directory.is_empty():
+ for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
+ if enabled_plugin.contains(plugin_directory):
+ var config := ConfigFile.new()
+ var error = config.load(enabled_plugin)
+ if error != OK:
+ break
+ return config.get_value("plugin", "version", NO_VERSION_STRING)
+ return ""
+
+func update_version_label() -> void:
+ var plugin_version = _get_plugin_version()
+ if plugin_version.is_empty():
+ plugin_version = NO_VERSION_STRING
+ text = version_prefix + plugin_version
+
+func _ready() -> void:
+ update_version_label()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/plugin_version_label.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/plugin_version_label.gd.uid
new file mode 100644
index 0000000..b9b91f0
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/labels/plugin_version_label.gd.uid
@@ -0,0 +1 @@
+uid://kgp5jnrxxhdy
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd
new file mode 100644
index 0000000..63ec7b3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd
@@ -0,0 +1,176 @@
+class_name LoadingScreen
+extends CanvasLayer
+## Scene for displaying the progress of a loading scene to the player.
+
+const STALLED_ON_WEB = "\nIf running in a browser, try clicking out of the window, \nand then click back into the window. It might unstick.\nLasty, you may try refreshing the page.\n\n"
+
+enum StallStage{STARTED, WAITING, STILL_WAITING, GIVE_UP}
+
+## Delay between updating the message in the window during stalled periods.
+@export_range(5, 60, 0.5, "or_greater") var state_change_delay : float = 15.0
+@export_group("State Messages")
+@export_subgroup("In Progress")
+## Default text to show when loading.
+@export var _in_progress : String = "Loading..."
+## Next text to show when loading has stalled.
+@export var _in_progress_waiting : String = "Still Loading..."
+## Last text to show when loading has stalled.
+@export var _in_progress_still_waiting : String = "Still Loading... (%d seconds)"
+@export_subgroup("Completed")
+## Default text to show when loading has completed.
+@export var _complete : String = "Loading Complete!"
+## Next text to show if opening the scene has stalled.
+@export var _complete_waiting : String = "Any Moment Now..."
+## Last text to show if opening the scene has stalled.
+@export var _complete_still_waiting : String = "Any Moment Now... (%d seconds)"
+
+var _stall_stage : StallStage = StallStage.STARTED
+var _scene_loading_complete : bool = false
+var _scene_loading_progress : float = 0.0 :
+ set(value):
+ var _value_changed = _scene_loading_progress != value
+ _scene_loading_progress = value
+ if _value_changed:
+ update_total_loading_progress()
+ _reset_loading_stage()
+var _total_loading_progress : float = 0.0 :
+ set(value):
+ _total_loading_progress = value
+ %ProgressBar.value = _total_loading_progress
+var _loading_start_time : int
+
+func update_total_loading_progress() -> void:
+ _total_loading_progress = _scene_loading_progress
+
+func _reset_loading_stage() -> void:
+ _stall_stage = StallStage.STARTED
+ %LoadingTimer.start(state_change_delay)
+
+func _reset_loading_start_time() -> void:
+ _loading_start_time = Time.get_ticks_msec()
+
+func _get_seconds_waiting() -> int:
+ return int((Time.get_ticks_msec() - _loading_start_time) / 1000.0)
+
+func _update_scene_loading_progress() -> void:
+ var new_progress = SceneLoader.get_progress()
+ if new_progress > _scene_loading_progress:
+ _scene_loading_progress = new_progress
+
+func _set_scene_loading_complete() -> void:
+ _scene_loading_progress = 1.0
+ _scene_loading_complete = true
+
+func _reset_scene_loading_progress() -> void:
+ _scene_loading_progress = 0.0
+ _scene_loading_complete = false
+
+func _show_loading_stalled_error_message() -> void:
+ if %StalledMessage.visible:
+ return
+ if _scene_loading_progress == 0:
+ %StalledMessage.dialog_text = "Stalled at start. You may try waiting or restarting.\n"
+ else:
+ %StalledMessage.dialog_text = "Stalled at %d%%. You may try waiting or restarting.\n" % (_scene_loading_progress * 100.0)
+ if OS.has_feature("web"):
+ %StalledMessage.dialog_text += STALLED_ON_WEB
+ %StalledMessage.popup()
+
+func _show_scene_switching_error_message() -> void:
+ if %ErrorMessage.visible:
+ return
+ %ErrorMessage.dialog_text = "Loading Error: Failed to switch scenes."
+ %ErrorMessage.popup()
+
+func _hide_popups() -> void:
+ %ErrorMessage.hide()
+ %StalledMessage.hide()
+
+func get_progress_message() -> String:
+ var _progress_message : String
+ match _stall_stage:
+ StallStage.STARTED:
+ if _scene_loading_complete:
+ _progress_message = _complete
+ else:
+ _progress_message = _in_progress
+ StallStage.WAITING:
+ if _scene_loading_complete:
+ _progress_message = _complete_waiting
+ else:
+ _progress_message = _in_progress_waiting
+ StallStage.STILL_WAITING, StallStage.GIVE_UP:
+ if _scene_loading_complete:
+ _progress_message = _complete_still_waiting
+ else:
+ _progress_message = _in_progress_still_waiting
+ if _progress_message.contains("%d"):
+ _progress_message = _progress_message % _get_seconds_waiting()
+ return _progress_message
+
+func _update_progress_messaging() -> void:
+ %ProgressLabel.text = get_progress_message()
+ if _stall_stage == StallStage.GIVE_UP:
+ if _scene_loading_complete:
+ _show_scene_switching_error_message()
+ else:
+ _show_loading_stalled_error_message()
+ else:
+ _hide_popups()
+
+func _process(_delta : float) -> void:
+ var status = SceneLoader.get_status()
+ match(status):
+ ResourceLoader.THREAD_LOAD_IN_PROGRESS:
+ _update_scene_loading_progress()
+ _update_progress_messaging()
+ ResourceLoader.THREAD_LOAD_LOADED:
+ _set_scene_loading_complete()
+ _update_progress_messaging()
+ ResourceLoader.THREAD_LOAD_FAILED:
+ %ErrorMessage.dialog_text = "Loading Error: %d" % status
+ %ErrorMessage.popup()
+ set_process(false)
+ ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
+ _hide_popups()
+ set_process(false)
+
+func _on_loading_timer_timeout() -> void:
+ var prev_stage : StallStage = _stall_stage
+ match prev_stage:
+ StallStage.STARTED:
+ _stall_stage = StallStage.WAITING
+ %LoadingTimer.start(state_change_delay)
+ StallStage.WAITING:
+ _stall_stage = StallStage.STILL_WAITING
+ %LoadingTimer.start(state_change_delay)
+ StallStage.STILL_WAITING:
+ _stall_stage = StallStage.GIVE_UP
+
+func _reload_main_scene_or_quit() -> void:
+ var err = get_tree().change_scene_to_file(ProjectSettings.get_setting("application/run/main_scene"))
+ if err:
+ push_error("failed to load main scene: %d" % err)
+ get_tree().quit()
+
+func _on_error_message_confirmed() -> void:
+ _reload_main_scene_or_quit()
+
+func _on_confirmation_dialog_canceled() -> void:
+ _reload_main_scene_or_quit()
+
+func _on_confirmation_dialog_confirmed() -> void:
+ _reset_loading_stage()
+
+func reset() -> void:
+ show()
+ _reset_loading_stage()
+ _reset_scene_loading_progress()
+ _reset_loading_start_time()
+ _hide_popups()
+ set_process(true)
+
+func close() -> void:
+ set_process(false)
+ _hide_popups()
+ hide()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd.uid
new file mode 100644
index 0000000..eada645
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd.uid
@@ -0,0 +1 @@
+uid://dgeewyjjpk4qn
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.tscn
new file mode 100644
index 0000000..fa20de3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.tscn
@@ -0,0 +1,87 @@
+[gd_scene format=3 uid="uid://cd0jbh4metflb"]
+
+[ext_resource type="Script" uid="uid://dgeewyjjpk4qn" path="res://addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.gd" id="1_gbk34"]
+
+[node name="LoadingScreen" type="CanvasLayer" unique_id=788661482]
+process_mode = 3
+layer = 20
+script = ExtResource("1_gbk34")
+
+[node name="Control" type="Control" parent="." unique_id=1999015458]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="BackPanel" type="Panel" parent="Control" unique_id=604252196]
+layout_mode = 0
+anchor_right = 1.0
+anchor_bottom = 1.0
+
+[node name="BackgroundColor" type="ColorRect" parent="Control" unique_id=144482895]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 0)
+
+[node name="BackgroundTextureRect" type="TextureRect" parent="Control" unique_id=630614432]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+stretch_mode = 5
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Control" unique_id=10456653]
+layout_mode = 0
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_left = 30.0
+offset_top = -23.0
+offset_right = -30.0
+offset_bottom = 98.0
+theme_override_constants/separation = 50
+
+[node name="ProgressLabel" type="Label" parent="Control/VBoxContainer" unique_id=1503629466]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Loading..."
+horizontal_alignment = 1
+
+[node name="ProgressBar" type="ProgressBar" parent="Control/VBoxContainer" unique_id=1483901811]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 50)
+layout_mode = 2
+max_value = 1.0
+
+[node name="ErrorMessage" type="AcceptDialog" parent="Control" unique_id=1421740495]
+unique_name_in_owner = true
+title = "Loading Error"
+initial_position = 2
+size = Vector2i(360, 100)
+
+[node name="StalledMessage" type="ConfirmationDialog" parent="Control" unique_id=1733180994]
+unique_name_in_owner = true
+title = "Loading Stalled"
+initial_position = 2
+size = Vector2i(360, 100)
+ok_button_text = "Try Waiting"
+cancel_button_text = "Reload"
+
+[node name="LoadingTimer" type="Timer" parent="." unique_id=1602373273]
+unique_name_in_owner = true
+one_shot = true
+autostart = true
+
+[connection signal="confirmed" from="Control/ErrorMessage" to="." method="_on_error_message_confirmed"]
+[connection signal="canceled" from="Control/StalledMessage" to="." method="_on_confirmation_dialog_canceled"]
+[connection signal="confirmed" from="Control/StalledMessage" to="." method="_on_confirmation_dialog_confirmed"]
+[connection signal="timeout" from="LoadingTimer" to="." method="_on_loading_timer_timeout"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd
new file mode 100644
index 0000000..abbd1f3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd
@@ -0,0 +1,127 @@
+class_name MainMenu
+extends Control
+## Base menu scene that links to a game scene, an options menu, and credits.
+
+signal sub_menu_opened
+signal sub_menu_closed
+signal game_started
+signal game_exited
+
+## Defines the path to the game scene. Hides the play button if empty.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var game_scene_path : String
+## The scene to open when a player clicks the 'Options' button.
+@export var options_packed_scene : PackedScene
+## The scene to open when a player clicks the 'Credits' button.
+@export var credits_packed_scene : PackedScene
+@export var confirm_exit : bool = true
+@export_group("Extra Settings")
+## If true, signals that the game has started loading in the background, instead of directly loading it.
+@export var signal_game_start : bool = false
+## If true, signals that the player clicked the 'Exit' button, instead of immediately exiting.
+@export var signal_game_exit : bool = false
+
+var sub_menu : Control
+
+@onready var menu_container = %MenuContainer
+@onready var menu_buttons_box_container = %MenuButtonsBoxContainer
+@onready var new_game_button = %NewGameButton
+@onready var options_button = %OptionsButton
+@onready var credits_button = %CreditsButton
+@onready var exit_button = %ExitButton
+@onready var exit_confirmation = %ExitConfirmation
+
+func get_game_scene_path() -> String:
+ if game_scene_path.is_empty():
+ return AppConfig.game_scene_path
+ return game_scene_path
+
+func load_game_scene() -> void:
+ if signal_game_start:
+ SceneLoader.load_scene(get_game_scene_path(), true)
+ game_started.emit()
+ else:
+ SceneLoader.load_scene(get_game_scene_path())
+
+func new_game() -> void:
+ load_game_scene()
+
+func try_exit_game() -> void:
+ if confirm_exit and (not exit_confirmation.visible):
+ exit_confirmation.show()
+ else:
+ exit_game()
+
+func exit_game() -> void:
+ if OS.has_feature("web"):
+ return
+ if signal_game_exit:
+ game_exited.emit()
+ else:
+ get_tree().quit()
+
+func _open_sub_menu(menu : PackedScene) -> Node:
+ sub_menu = menu.instantiate()
+ add_child(sub_menu)
+ menu_container.hide()
+ sub_menu.hidden.connect(_close_sub_menu, CONNECT_ONE_SHOT)
+ sub_menu.tree_exiting.connect(_close_sub_menu, CONNECT_ONE_SHOT)
+ sub_menu_opened.emit()
+ return sub_menu
+
+func _close_sub_menu() -> void:
+ if sub_menu == null:
+ return
+ sub_menu.queue_free()
+ sub_menu = null
+ menu_container.show()
+ sub_menu_closed.emit()
+
+func _event_is_mouse_button_released(event : InputEvent) -> bool:
+ return event is InputEventMouseButton and not event.is_pressed()
+
+func _input(event : InputEvent) -> void:
+ if event.is_action_released("ui_cancel"):
+ if sub_menu:
+ _close_sub_menu()
+ else:
+ try_exit_game()
+ if event.is_action_released("ui_accept") and get_viewport().gui_get_focus_owner() == null:
+ menu_buttons_box_container.focus_first()
+
+func _hide_exit_for_web() -> void:
+ if OS.has_feature("web"):
+ exit_button.hide()
+
+func _hide_new_game_if_unset() -> void:
+ if get_game_scene_path().is_empty():
+ new_game_button.hide()
+
+func _hide_options_if_unset() -> void:
+ if options_packed_scene == null:
+ options_button.hide()
+
+func _hide_credits_if_unset() -> void:
+ if credits_packed_scene == null:
+ credits_button.hide()
+
+func _ready() -> void:
+ _hide_exit_for_web()
+ _hide_options_if_unset()
+ _hide_credits_if_unset()
+ _hide_new_game_if_unset()
+
+func _on_new_game_button_pressed() -> void:
+ new_game()
+
+func _on_options_button_pressed() -> void:
+ _open_sub_menu(options_packed_scene)
+
+func _on_credits_button_pressed() -> void:
+ _open_sub_menu(credits_packed_scene)
+
+func _on_exit_button_pressed() -> void:
+ try_exit_game()
+
+func _on_exit_confirmation_confirmed():
+ exit_game()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd.uid
new file mode 100644
index 0000000..905b7fd
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd.uid
@@ -0,0 +1 @@
+uid://bhgs1upaahk3y
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.tscn
new file mode 100644
index 0000000..342f1e5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.tscn
@@ -0,0 +1,182 @@
+[gd_scene format=3 uid="uid://c6k5nnpbypshi"]
+
+[ext_resource type="Script" uid="uid://bhgs1upaahk3y" path="res://addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.gd" id="1"]
+[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="4_l1ebe"]
+[ext_resource type="PackedScene" uid="uid://bkcsjsk2ciff" path="res://addons/maaacks_game_template/base/nodes/music_players/background_music_player.tscn" id="4_w8sbm"]
+[ext_resource type="Script" uid="uid://b5oej1q4h7jvh" path="res://addons/maaacks_game_template/base/nodes/autoloads/ui_sound_controller/ui_sound_controller.gd" id="6_bs342"]
+[ext_resource type="Script" uid="uid://dmkubt2nsnsbn" path="res://addons/maaacks_game_template/base/nodes/labels/config_version_label.gd" id="6_pdiij"]
+[ext_resource type="PackedScene" uid="uid://cwt4p3bufkke5" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="7_im16j"]
+[ext_resource type="Script" uid="uid://bkwlopi4qn32o" path="res://addons/maaacks_game_template/base/nodes/labels/config_name_label.gd" id="7_j7612"]
+
+[node name="MainMenu" type="Control" unique_id=781937481]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1")
+
+[node name="UISoundController" type="Node" parent="." unique_id=1619985599]
+script = ExtResource("6_bs342")
+
+[node name="BackgroundMusicPlayer" parent="." unique_id=1141822549 instance=ExtResource("4_w8sbm")]
+
+[node name="BackgroundTextureRect" type="TextureRect" parent="." unique_id=1940176448]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+stretch_mode = 5
+
+[node name="MenuContainer" type="MarginContainer" parent="." unique_id=396616752]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="TitleMargin" type="MarginContainer" parent="MenuContainer" unique_id=2112060331]
+layout_mode = 2
+theme_override_constants/margin_top = 24
+
+[node name="TitleContainer" type="Control" parent="MenuContainer/TitleMargin" unique_id=556427956]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="TitleLabel" type="Label" parent="MenuContainer/TitleMargin/TitleContainer" unique_id=359406872]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 67.0
+grow_horizontal = 2
+theme_override_font_sizes/font_size = 48
+text = "Title"
+horizontal_alignment = 1
+vertical_alignment = 1
+script = ExtResource("7_j7612")
+
+[node name="SubTitleMargin" type="MarginContainer" parent="MenuContainer" unique_id=1510335937]
+layout_mode = 2
+theme_override_constants/margin_top = 92
+
+[node name="SubTitleContainer" type="Control" parent="MenuContainer/SubTitleMargin" unique_id=930520368]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="SubTitleLabel" type="Label" parent="MenuContainer/SubTitleMargin/SubTitleContainer" unique_id=1037003361]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 34.0
+grow_horizontal = 2
+theme_override_font_sizes/font_size = 24
+text = "Subtitle"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="MenuButtonsMargin" type="MarginContainer" parent="MenuContainer" unique_id=210553886]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/margin_top = 136
+theme_override_constants/margin_bottom = 8
+
+[node name="MenuButtonsContainer" type="Control" parent="MenuContainer/MenuButtonsMargin" unique_id=116448484]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="MenuButtonsBoxContainer" type="BoxContainer" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer" unique_id=2017477786]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -64.0
+offset_top = -104.0
+offset_right = 64.0
+offset_bottom = 104.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 4
+theme_override_constants/separation = 16
+alignment = 1
+vertical = true
+script = ExtResource("4_l1ebe")
+
+[node name="NewGameButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" unique_id=2044537792]
+unique_name_in_owner = true
+layout_mode = 2
+text = "New Game"
+
+[node name="OptionsButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" unique_id=1486236076]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Options"
+
+[node name="CreditsButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" unique_id=301223169]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Credits"
+
+[node name="ExitButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" unique_id=1750454379]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Exit"
+
+[node name="VersionMargin" type="MarginContainer" parent="." unique_id=1732528471]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
+
+[node name="VersionContainer" type="Control" parent="VersionMargin" unique_id=1603969262]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="VersionLabel" type="Label" parent="VersionMargin/VersionContainer" unique_id=793266470]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -88.0
+offset_top = -26.0
+grow_horizontal = 0
+grow_vertical = 0
+text = "v0.0.0"
+horizontal_alignment = 2
+script = ExtResource("6_pdiij")
+
+[node name="ExitConfirmation" parent="." unique_id=912502155 instance=ExtResource("7_im16j")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(300, 160)
+layout_mode = 1
+offset_left = -150.0
+offset_top = -80.0
+offset_right = 150.0
+offset_bottom = 80.0
+ui_cancel_closes = false
+text = "Really exit the game?"
+title_visible = false
+
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/CreditsButton" to="." method="_on_credits_button_pressed"]
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"]
+[connection signal="confirmed" from="ExitConfirmation" to="." method="_on_exit_confirmation_confirmed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd
new file mode 100644
index 0000000..1e0caa5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd
@@ -0,0 +1,38 @@
+extends Control
+
+## Scene for adjusting the volume of the audio busses.
+@export var audio_control_scene : PackedScene
+## Optional names of audio busses that should be ignored.
+@export var hide_busses : Array[String]
+
+@onready var mute_control = %MuteControl
+
+func _on_bus_changed(bus_value : float, bus_iter : int) -> void:
+ AppSettings.set_bus_volume(bus_iter, bus_value)
+
+func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int) -> void:
+ if audio_control_scene == null or bus_name in hide_busses or bus_name.begins_with(AppSettings.SYSTEM_BUS_NAME_PREFIX):
+ return
+ var audio_control = audio_control_scene.instantiate()
+ %AudioControlContainer.call_deferred("add_child", audio_control)
+ if audio_control is OptionControl:
+ audio_control.option_section = OptionControl.OptionSections.AUDIO
+ audio_control.option_name = bus_name
+ audio_control.value = bus_value
+ audio_control.connect("setting_changed", _on_bus_changed.bind(bus_iter))
+
+func _add_audio_bus_controls() -> void:
+ for bus_iter in AudioServer.bus_count:
+ var bus_name : String = AppSettings.get_audio_bus_name(bus_iter)
+ var linear : float = AppSettings.get_bus_volume(bus_iter)
+ _add_audio_control(bus_name, linear, bus_iter)
+
+func _update_ui() -> void:
+ _add_audio_bus_controls()
+ mute_control.value = AppSettings.is_muted()
+
+func _ready() -> void:
+ _update_ui()
+
+func _on_mute_control_setting_changed(value : bool) -> void:
+ AppSettings.set_mute(value)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd.uid
new file mode 100644
index 0000000..4e3c290
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd.uid
@@ -0,0 +1 @@
+uid://bwugqn2cjr41e
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.tscn
new file mode 100644
index 0000000..d9d557d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.tscn
@@ -0,0 +1,42 @@
+[gd_scene format=3 uid="uid://c8vnncjwqcpab"]
+
+[ext_resource type="Script" uid="uid://bwugqn2cjr41e" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.gd" id="1"]
+[ext_resource type="PackedScene" uid="uid://cl416gdb1fgwr" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn" id="2_raehj"]
+[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="3_dtraq"]
+[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_ojfec"]
+
+[node name="Audio" type="MarginContainer" unique_id=2801527]
+custom_minimum_size = Vector2(305, 0)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_top = 24
+theme_override_constants/margin_bottom = 24
+script = ExtResource("1")
+audio_control_scene = ExtResource("2_raehj")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1265848630]
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 2
+size_flags_horizontal = 4
+theme_override_constants/separation = 8
+alignment = 1
+script = ExtResource("3_dtraq")
+search_depth = 3
+
+[node name="AudioControlContainer" type="VBoxContainer" parent="VBoxContainer" unique_id=2086522930]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/separation = 8
+
+[node name="MuteControl" parent="VBoxContainer" unique_id=372694560 instance=ExtResource("4_ojfec")]
+unique_name_in_owner = true
+layout_mode = 2
+option_name = "Mute"
+option_section = 2
+key = "Mute"
+section = "AudioSettings"
+
+[connection signal="setting_changed" from="VBoxContainer/MuteControl" to="." method="_on_mute_control_setting_changed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd
new file mode 100644
index 0000000..9f77205
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd
@@ -0,0 +1,349 @@
+@tool
+class_name InputActionsList
+extends Container
+## Scene to list the input actions out as buttons in a grid format.
+
+const EMPTY_INPUT_ACTION_STRING = " "
+
+signal already_assigned(action_name : String, input_name : String)
+signal minimum_reached(action_name : String)
+signal button_clicked(action_name : String, readable_input_name : String)
+
+const BUTTON_NAME_GROUP_STRING : String = "%s:%d"
+
+## If true, lists action names on the vertical axis.
+@export var vertical : bool = true :
+ set(value):
+ vertical = value
+ if is_inside_tree():
+ %ParentBoxContainer.vertical = vertical
+## The number of inputs to make editable per action name.
+@export_range(1, 5) var action_groups : int = 2
+## The header to each input action group.
+@export var action_group_names : Array[String]
+## The names of the action names that should be listed for editing.
+@export var input_action_names : Array[StringName] :
+ set(value):
+ var _value_changed = input_action_names != value
+ input_action_names = value
+ if _value_changed:
+ _refresh_readable_action_names()
+## The readable names of the action names that should be listed for editing.
+@export var readable_action_names : Array[String] :
+ set(value):
+ var _value_changed = readable_action_names != value
+ readable_action_names = value
+ if _value_changed:
+ var _new_action_name_map : Dictionary
+ for iter in range(input_action_names.size()):
+ var _input_name : StringName = input_action_names[iter]
+ var _readable_name : String = readable_action_names[iter]
+ _new_action_name_map[_input_name] = _readable_name
+ action_name_map = _new_action_name_map
+## If true, capitalizes action names in order to make them readable.
+@export var capitalize_action_names : bool = true :
+ set(value):
+ capitalize_action_names = value
+ _refresh_readable_action_names()
+## If true, show action names that are not explicitely listed in an input action name map.
+@export var show_all_actions : bool = true
+## Optional minimum size to add to all edit buttons.
+@export var button_minimum_size : Vector2
+@export_group("Icons")
+## Optional link to an input icon mapper to replace the text with icons.
+@export var input_icon_mapper : InputIconMapper
+## If true, expand the icons to fill the buttons.
+@export var expand_icon : bool = false
+@export_group("Built-in Actions")
+## Shows Godot's built-in actions (action names starting with "ui_") in the tree.
+@export var show_built_in_actions : bool = false
+## Prevents assigning inputs that are already assigned to Godot's built-in actions (action names starting with "ui_"). Not recommended.
+@export var catch_built_in_duplicate_inputs : bool = false
+## Maps the names of built-in input actions to readable names for users.
+@export var built_in_action_name_map := InputEventHelper.BUILT_IN_ACTION_NAME_MAP
+@export_group("Debug")
+## Maps the names of input actions to readable names for users.
+@export var action_name_map : Dictionary
+
+var action_button_map : Dictionary = {}
+var button_readable_input_map : Dictionary = {}
+var assigned_input_events : Dictionary = {}
+var editing_action_name : String = ""
+var editing_action_group : int = 0
+var last_input_readable_name
+
+func _refresh_readable_action_names():
+ var _new_readable_action_names : Array[String]
+ for action_name in input_action_names:
+ if capitalize_action_names:
+ action_name = action_name.capitalize()
+ _new_readable_action_names.append(action_name)
+ readable_action_names = _new_readable_action_names
+
+func _clear_list() -> void:
+ for child in %ParentBoxContainer.get_children():
+ if child == %ActionBoxContainer:
+ continue
+ child.queue_free()
+
+func _replace_action(action_name : String, readable_input_name : String = "") -> void:
+ var readable_action_name = tr(_get_action_readable_name(action_name))
+ button_clicked.emit(readable_action_name, readable_input_name)
+
+func _on_button_pressed(action_name : String, action_group : int) -> void:
+ editing_action_name = action_name
+ editing_action_group = action_group
+ var button = _get_button_by_action(action_name, action_group)
+ var readable_input_name : String
+ if button and button in button_readable_input_map:
+ readable_input_name = button_readable_input_map[button]
+ _replace_action(action_name, readable_input_name)
+
+func _new_action_box() -> Node:
+ var new_action_box : Node = %ActionBoxContainer.duplicate()
+ new_action_box.visible = true
+ new_action_box.vertical = !(vertical)
+ return new_action_box
+
+func _add_header() -> void:
+ if action_group_names.is_empty(): return
+ var new_action_box := _new_action_box()
+ for group_iter in range(action_groups):
+ var group_name := ""
+ if group_iter < action_group_names.size():
+ group_name = action_group_names[group_iter]
+ var new_label := Label.new()
+ if button_minimum_size.x > 0:
+ new_label.custom_minimum_size.x = button_minimum_size.x
+ new_label.size_flags_horizontal = SIZE_SHRINK_CENTER
+ else:
+ new_label.size_flags_horizontal = SIZE_EXPAND_FILL
+ if button_minimum_size.y > 0:
+ new_label.custom_minimum_size.y = button_minimum_size.y
+ new_label.size_flags_vertical = SIZE_SHRINK_CENTER
+ else:
+ new_label.size_flags_vertical = SIZE_EXPAND_FILL
+ new_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ new_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
+ new_label.text = group_name
+ new_action_box.add_child(new_label)
+ %ParentBoxContainer.add_child(new_action_box)
+
+func _add_to_action_button_map(action_name : String, action_group : int, button_node : BaseButton) -> void:
+ var key_string : String = BUTTON_NAME_GROUP_STRING % [action_name, action_group]
+ action_button_map[key_string] = button_node
+
+func _get_button_by_action(action_name : String, action_group : int) -> Button:
+ var key_string : String = BUTTON_NAME_GROUP_STRING % [action_name, action_group]
+ if key_string in action_button_map:
+ return action_button_map[key_string]
+ return null
+
+func _update_next_button_disabled_state(action_name : String, action_group : int, disabled: bool = false) -> void:
+ var button = _get_button_by_action(action_name, action_group + 1)
+ if button:
+ button.disabled = disabled
+
+func _update_assigned_inputs_and_button(action_name : String, action_group : int, input_event : InputEvent) -> void:
+ var new_readable_input_name = InputEventHelper.get_text(input_event)
+ var button = _get_button_by_action(action_name, action_group)
+ if not button: return
+ var icon : Texture
+ if input_icon_mapper:
+ icon = input_icon_mapper.get_icon(input_event)
+ if icon:
+ button.icon = icon
+ else:
+ button.icon = null
+ if button.icon == null:
+ button.text = new_readable_input_name
+ else:
+ button.text = ""
+ var old_readable_input_name : String
+ if button in button_readable_input_map:
+ old_readable_input_name = button_readable_input_map[button]
+ assigned_input_events.erase(old_readable_input_name)
+ button_readable_input_map[button] = new_readable_input_name
+ assigned_input_events[new_readable_input_name] = action_name
+
+func _clear_button(action_name : String, action_group : int) -> void:
+ var button = _get_button_by_action(action_name, action_group)
+ if not button: return
+ button.icon = null
+ button.text = EMPTY_INPUT_ACTION_STRING
+ var old_readable_input_name : String
+ if button in button_readable_input_map:
+ old_readable_input_name = button_readable_input_map[button]
+ assigned_input_events.erase(old_readable_input_name)
+ button_readable_input_map[button] = EMPTY_INPUT_ACTION_STRING
+
+func _add_new_button(content : Variant, container: Control, disabled : bool = false) -> Button:
+ var new_button := Button.new()
+ if button_minimum_size.x > 0:
+ new_button.custom_minimum_size.x = button_minimum_size.x
+ new_button.size_flags_horizontal = SIZE_SHRINK_CENTER
+ else:
+ new_button.size_flags_horizontal = SIZE_EXPAND_FILL
+ if button_minimum_size.y > 0:
+ new_button.custom_minimum_size.y = button_minimum_size.y
+ new_button.size_flags_vertical = SIZE_SHRINK_CENTER
+ else:
+ new_button.size_flags_vertical = SIZE_EXPAND_FILL
+ new_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ new_button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
+ new_button.expand_icon = expand_icon
+ if content is Texture:
+ new_button.icon = content
+ elif content is String:
+ new_button.text = content
+ new_button.disabled = disabled
+ container.add_child(new_button)
+ return new_button
+
+func _connect_button_and_add_to_maps(button : Button, input_name : String, action_name : String, group_iter : int) -> void:
+ button.pressed.connect(_on_button_pressed.bind(action_name, group_iter))
+ button_readable_input_map[button] = input_name
+ _add_to_action_button_map(action_name, group_iter, button)
+
+func _add_action_options(action_name : String, readable_action_name : String, input_events : Array[InputEvent]) -> void:
+ var new_action_box = %ActionBoxContainer.duplicate()
+ new_action_box.visible = true
+ new_action_box.vertical = !(vertical)
+ new_action_box.get_child(0).text = readable_action_name
+ for group_iter in range(action_groups):
+ var input_event : InputEvent
+ if group_iter < input_events.size():
+ input_event = input_events[group_iter]
+ var text = InputEventHelper.get_text(input_event)
+ var is_disabled = group_iter > input_events.size()
+ if text.is_empty(): text = EMPTY_INPUT_ACTION_STRING
+ var icon : Texture
+ if input_icon_mapper:
+ icon = input_icon_mapper.get_icon(input_event)
+ var content = icon if icon else text
+ var button : Button = _add_new_button(content, new_action_box, is_disabled)
+ _connect_button_and_add_to_maps(button, text, action_name, group_iter)
+ %ParentBoxContainer.add_child(new_action_box)
+
+func _get_all_action_names(include_built_in : bool = false) -> Array[StringName]:
+ var action_names : Array[StringName] = input_action_names.duplicate()
+ var full_action_name_map = action_name_map.duplicate()
+ if include_built_in:
+ for action_name in built_in_action_name_map:
+ if action_name is String:
+ action_name = StringName(action_name)
+ if action_name is StringName:
+ action_names.append(action_name)
+ if show_all_actions:
+ var all_actions := AppSettings.get_action_names(include_built_in)
+ for action_name in all_actions:
+ if not action_name in action_names:
+ action_names.append(action_name)
+ return action_names
+
+func _get_action_readable_name(action_name : StringName) -> String:
+ var readable_name : String
+ if action_name in action_name_map:
+ readable_name = action_name_map[action_name]
+ elif action_name in built_in_action_name_map:
+ readable_name = built_in_action_name_map[action_name]
+ else:
+ readable_name = action_name
+ if capitalize_action_names:
+ readable_name = readable_name.capitalize()
+ action_name_map[action_name] = readable_name
+ return readable_name
+
+func _build_ui_list() -> void:
+ _clear_list()
+ _add_header()
+ var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
+ for action_name in action_names:
+ var input_events = InputMap.action_get_events(action_name)
+ if input_events.size() < 1:
+ continue
+ var readable_name : String = _get_action_readable_name(action_name)
+ _add_action_options(action_name, readable_name, input_events)
+
+func _assign_input_event(input_event : InputEvent, action_name : String) -> void:
+ assigned_input_events[InputEventHelper.get_text(input_event)] = action_name
+
+func _assign_input_event_to_action_group(input_event : InputEvent, action_name : String, action_group : int) -> void:
+ _assign_input_event(input_event, action_name)
+ var action_events := InputMap.action_get_events(action_name)
+ action_events.resize(action_events.size() + 1)
+ action_events[action_group] = input_event
+ InputMap.action_erase_events(action_name)
+ var final_action_events : Array[InputEvent]
+ for input_action_event in action_events:
+ if input_action_event == null: continue
+ final_action_events.append(input_action_event)
+ InputMap.action_add_event(action_name, input_action_event)
+ AppSettings.set_config_input_events(action_name, final_action_events)
+ action_group = min(action_group, final_action_events.size() - 1)
+ _update_assigned_inputs_and_button(action_name, action_group, input_event)
+ _update_next_button_disabled_state(action_name, action_group)
+
+func _build_assigned_input_events() -> void:
+ assigned_input_events.clear()
+ var action_names := _get_all_action_names(show_built_in_actions and catch_built_in_duplicate_inputs)
+ for action_name in action_names:
+ var input_events = InputMap.action_get_events(action_name)
+ for input_event in input_events:
+ _assign_input_event(input_event, action_name)
+
+func _get_action_for_input_event(input_event : InputEvent) -> String:
+ if InputEventHelper.get_text(input_event) in assigned_input_events:
+ return assigned_input_events[InputEventHelper.get_text(input_event)]
+ return ""
+
+func add_action_event(last_input_text : String, last_input_event : InputEvent) -> void:
+ last_input_readable_name = last_input_text
+ if last_input_event != null:
+ var assigned_action := _get_action_for_input_event(last_input_event)
+ if not assigned_action.is_empty():
+ var readable_action_name = tr(_get_action_readable_name(assigned_action))
+ already_assigned.emit(readable_action_name, last_input_readable_name)
+ else:
+ _assign_input_event_to_action_group(last_input_event, editing_action_name, editing_action_group)
+ editing_action_name = ""
+
+func _refresh_ui_list_button_content() -> void:
+ var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
+ for action_name in action_names:
+ var input_events := InputMap.action_get_events(action_name)
+ if input_events.size() < 1:
+ continue
+ var group_iter : int = 0
+ for input_event in input_events:
+ _update_assigned_inputs_and_button(action_name, group_iter, input_event)
+ _update_next_button_disabled_state(action_name, group_iter)
+ group_iter += 1
+ while group_iter < action_groups:
+ _clear_button(action_name, group_iter)
+ _update_next_button_disabled_state(action_name, group_iter, true)
+ group_iter += 1
+
+func _set_action_box_container_size() -> void:
+ if button_minimum_size.x > 0:
+ %ActionBoxContainer.size_flags_horizontal = SIZE_SHRINK_CENTER
+ else:
+ %ActionBoxContainer.size_flags_horizontal = SIZE_EXPAND_FILL
+ if button_minimum_size.y > 0:
+ %ActionBoxContainer.size_flags_vertical = SIZE_SHRINK_CENTER
+ else:
+ %ActionBoxContainer.size_flags_vertical = SIZE_EXPAND_FILL
+
+func reset() -> void:
+ AppSettings.reset_to_default_inputs()
+ _build_assigned_input_events()
+ _refresh_ui_list_button_content()
+
+func _ready() -> void:
+ if Engine.is_editor_hint(): return
+ vertical = vertical
+ _set_action_box_container_size()
+ _build_assigned_input_events()
+ _build_ui_list.call_deferred()
+ if input_icon_mapper:
+ input_icon_mapper.joypad_device_changed.connect(_refresh_ui_list_button_content)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd.uid
new file mode 100644
index 0000000..920d4f8
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd.uid
@@ -0,0 +1 @@
+uid://b3q5fgjev8gyo
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.tscn
new file mode 100644
index 0000000..dbd7ee3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.tscn
@@ -0,0 +1,43 @@
+[gd_scene format=3 uid="uid://bxp45814v6ydv"]
+
+[ext_resource type="Script" uid="uid://b3q5fgjev8gyo" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.gd" id="1_cxorh"]
+
+[node name="InputActionsList" type="ScrollContainer" unique_id=1135275701]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+follow_focus = true
+script = ExtResource("1_cxorh")
+action_groups = 3
+action_group_names = Array[String](["Primary", "Secondary", "Tertiary", "Quaternary", "Quinary"])
+input_action_names = Array[StringName]([&"move_forward", &"move_backward", &"move_up", &"move_down", &"move_left", &"move_right", &"interact"])
+readable_action_names = Array[String](["Move Forward", "Move Backward", "Move Up", "Move Down", "Move Left", "Move Right", "Interact"])
+action_name_map = {
+&"interact": "Interact",
+&"move_backward": "Move Backward",
+&"move_down": "Move Down",
+&"move_forward": "Move Forward",
+&"move_left": "Move Left",
+&"move_right": "Move Right",
+&"move_up": "Move Up"
+}
+
+[node name="ParentBoxContainer" type="BoxContainer" parent="." unique_id=1544918220]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+vertical = true
+
+[node name="ActionBoxContainer" type="BoxContainer" parent="ParentBoxContainer" unique_id=694881838]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="ActionNameLabel" type="Label" parent="ParentBoxContainer/ActionBoxContainer" unique_id=1115508057]
+custom_minimum_size = Vector2(150, 0)
+layout_mode = 2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd
new file mode 100644
index 0000000..fa91297
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd
@@ -0,0 +1,232 @@
+@tool
+class_name InputActionsTree
+extends Tree
+## Scene to list the input actions out in a tree format.
+
+signal already_assigned(action_name : String, input_name : String)
+signal minimum_reached(action_name : String)
+signal add_button_clicked(action_name : String)
+signal remove_button_clicked(action_name : String, input_name : String)
+
+## The names of the action names that should be listed for editing.
+@export var input_action_names : Array[StringName] :
+ set(value):
+ var _value_changed = input_action_names != value
+ input_action_names = value
+ if _value_changed:
+ _refresh_readable_action_names()
+## The readable names of the action names that should be listed for editing.
+@export var readable_action_names : Array[String] :
+ set(value):
+ var _value_changed = readable_action_names != value
+ readable_action_names = value
+ if _value_changed:
+ var _new_action_name_map : Dictionary
+ for iter in range(input_action_names.size()):
+ var _input_name : StringName = input_action_names[iter]
+ var _readable_name : String = readable_action_names[iter]
+ _new_action_name_map[_input_name] = _readable_name
+ action_name_map = _new_action_name_map
+## If true, capitalizes action names in order to make them readable.
+@export var capitalize_action_names : bool = true :
+ set(value):
+ capitalize_action_names = value
+ _refresh_readable_action_names()
+## Show action names that are not explicitely listed in an action name map.
+@export var show_all_actions : bool = true
+@export_group("Icons")
+## Icon for the button that adds a new input to an action name.
+@export var add_button_texture : Texture2D
+## Icon for the button that removes an input to an action name.
+@export var remove_button_texture : Texture2D
+## Optional link to an input icon mapper to replace the text with icons.
+@export var input_icon_mapper : InputIconMapper
+@export_group("Built-in Actions")
+## Shows Godot's built-in actions (action names starting with "ui_") in the tree.
+@export var show_built_in_actions : bool = false
+## Prevents assigning inputs that are already assigned to Godot's built-in actions (action names starting with "ui_"). Not recommended.
+@export var catch_built_in_duplicate_inputs : bool = false
+## Maps the names of built-in input actions to readable names for users.
+@export var built_in_action_name_map := InputEventHelper.BUILT_IN_ACTION_NAME_MAP
+@export_group("Debug")
+## Maps the names of input actions to readable names for users.
+@export var action_name_map : Dictionary
+
+var tree_item_add_map : Dictionary = {}
+var tree_item_remove_map : Dictionary = {}
+var tree_item_action_map : Dictionary = {}
+var assigned_input_events : Dictionary = {}
+var editing_action_name : String = ""
+var editing_item
+var last_input_readable_name
+
+func _refresh_readable_action_names():
+ var _new_readable_action_names : Array[String]
+ for action_name in input_action_names:
+ if capitalize_action_names:
+ action_name = action_name.capitalize()
+ _new_readable_action_names.append(action_name)
+ readable_action_names = _new_readable_action_names
+
+func _start_tree() -> void:
+ clear()
+ create_item()
+
+func _add_input_event_as_tree_item(action_name : String, input_event : InputEvent, parent_item : TreeItem) -> void:
+ var input_tree_item : TreeItem = create_item(parent_item)
+ var icon : Texture
+ if input_icon_mapper:
+ icon = input_icon_mapper.get_icon(input_event)
+ if icon:
+ input_tree_item.set_icon(0, icon)
+ input_tree_item.set_text(0, InputEventHelper.get_text(input_event))
+ if remove_button_texture != null:
+ input_tree_item.add_button(0, remove_button_texture, -1, false, "Remove")
+ tree_item_remove_map[input_tree_item] = input_event
+ tree_item_action_map[input_tree_item] = action_name
+
+func _add_action_as_tree_item(readable_name : String, action_name : String, input_events : Array[InputEvent]) -> void:
+ var root_tree_item : TreeItem = get_root()
+ var action_tree_item : TreeItem = create_item(root_tree_item)
+ action_tree_item.set_text(0, readable_name)
+ tree_item_add_map[action_tree_item] = action_name
+ if add_button_texture != null:
+ action_tree_item.add_button(0, add_button_texture, -1, false, "Add")
+ for input_event in input_events:
+ _add_input_event_as_tree_item(action_name, input_event, action_tree_item)
+
+func _get_all_action_names(include_built_in : bool = false) -> Array[StringName]:
+ var action_names : Array[StringName] = input_action_names.duplicate()
+ var full_action_name_map = action_name_map.duplicate()
+ if include_built_in:
+ for action_name in built_in_action_name_map:
+ if action_name is String:
+ action_name = StringName(action_name)
+ if action_name is StringName:
+ action_names.append(action_name)
+ if show_all_actions:
+ var all_actions := AppSettings.get_action_names(include_built_in)
+ for action_name in all_actions:
+ if not action_name in action_names:
+ action_names.append(action_name)
+ return action_names
+
+func _get_action_readable_name(action_name : StringName) -> String:
+ var readable_name : String
+ if action_name in action_name_map:
+ readable_name = action_name_map[action_name]
+ elif action_name in built_in_action_name_map:
+ readable_name = built_in_action_name_map[action_name]
+ else:
+ readable_name = action_name
+ if capitalize_action_names:
+ readable_name = readable_name.capitalize()
+ action_name_map[action_name] = readable_name
+ return readable_name
+
+func _build_ui_tree() -> void:
+ _start_tree()
+ var action_names : Array[StringName] = _get_all_action_names(show_built_in_actions)
+ for action_name in action_names:
+ var input_events = InputMap.action_get_events(action_name)
+ if input_events.size() < 1:
+ continue
+ var readable_name : String = _get_action_readable_name(action_name)
+ _add_action_as_tree_item(readable_name, action_name, input_events)
+
+func _assign_input_event(input_event : InputEvent, action_name : String) -> void:
+ assigned_input_events[InputEventHelper.get_text(input_event)] = action_name
+
+func _assign_input_event_to_action(input_event : InputEvent, action_name : String) -> void:
+ _assign_input_event(input_event, action_name)
+ InputMap.action_add_event(action_name, input_event)
+ var action_events = InputMap.action_get_events(action_name)
+ AppSettings.set_config_input_events(action_name, action_events)
+ _add_input_event_as_tree_item(action_name, input_event, editing_item)
+
+func _can_remove_input_event(action_name : String) -> bool:
+ return InputMap.action_get_events(action_name).size() > 1
+
+func _remove_input_event(input_event : InputEvent) -> void:
+ assigned_input_events.erase(InputEventHelper.get_text(input_event))
+
+func _remove_input_event_from_action(input_event : InputEvent, action_name : String) -> void:
+ _remove_input_event(input_event)
+ AppSettings.remove_action_input_event(action_name, input_event)
+
+func _build_assigned_input_events() -> void:
+ assigned_input_events.clear()
+ var action_names := _get_all_action_names(show_built_in_actions and catch_built_in_duplicate_inputs)
+ for action_name in action_names:
+ var input_events = InputMap.action_get_events(action_name)
+ for input_event in input_events:
+ _assign_input_event(input_event, action_name)
+
+func _get_action_for_input_event(input_event : InputEvent) -> String:
+ if InputEventHelper.get_text(input_event) in assigned_input_events:
+ return assigned_input_events[InputEventHelper.get_text(input_event)]
+ return ""
+
+func add_action_event(last_input_text : String, last_input_event : InputEvent):
+ last_input_readable_name = last_input_text
+ if last_input_event != null:
+ var assigned_action := _get_action_for_input_event(last_input_event)
+ if not assigned_action.is_empty():
+ var readable_action_name = tr(_get_action_readable_name(assigned_action))
+ already_assigned.emit(readable_action_name, last_input_readable_name)
+ else:
+ _assign_input_event_to_action(last_input_event, editing_action_name)
+ editing_action_name = ""
+
+func remove_action_event(item : TreeItem) -> void:
+ if item not in tree_item_remove_map:
+ return
+ var action_name = tree_item_action_map[item]
+ var input_event = tree_item_remove_map[item]
+ if not _can_remove_input_event(action_name):
+ var readable_action_name = _get_action_readable_name(action_name)
+ minimum_reached.emit(readable_action_name)
+ return
+ _remove_input_event_from_action(input_event, action_name)
+ var parent_tree_item = item.get_parent()
+ parent_tree_item.remove_child(item)
+
+func reset() -> void:
+ AppSettings.reset_to_default_inputs()
+ _build_assigned_input_events()
+ _build_ui_tree()
+
+func _add_item(item : TreeItem) -> void:
+ editing_item = item
+ editing_action_name = tree_item_add_map[item]
+ var readable_action_name = tr(_get_action_readable_name(editing_action_name))
+ add_button_clicked.emit(readable_action_name)
+
+func _remove_item(item : TreeItem) -> void:
+ editing_item = item
+ editing_action_name = tree_item_action_map[item]
+ var readable_action_name = tr(_get_action_readable_name(editing_action_name))
+ var item_text = item.get_text(0)
+ remove_button_clicked.emit(readable_action_name, item_text)
+
+func _check_item_actions(item : TreeItem) -> void:
+ if item in tree_item_add_map:
+ _add_item(item)
+ elif item in tree_item_remove_map:
+ _remove_item(item)
+
+func _on_button_clicked(item : TreeItem, _column, _id, _mouse_button_index) -> void:
+ _check_item_actions(item)
+
+func _on_item_activated() -> void:
+ var item = get_selected()
+ _check_item_actions(item)
+
+func _ready() -> void:
+ if Engine.is_editor_hint(): return
+ _build_assigned_input_events()
+ _build_ui_tree.call_deferred()
+ button_clicked.connect(_on_button_clicked)
+ item_activated.connect(_on_item_activated)
+ if input_icon_mapper:
+ input_icon_mapper.joypad_device_changed.connect(_build_ui_tree)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd.uid
new file mode 100644
index 0000000..d09dd25
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd.uid
@@ -0,0 +1 @@
+uid://bp7d2e5djo2tp
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.tscn
new file mode 100644
index 0000000..1fd2415
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.tscn
@@ -0,0 +1,29 @@
+[gd_scene format=3 uid="uid://ci6wgl2ngd35n"]
+
+[ext_resource type="Script" uid="uid://bp7d2e5djo2tp" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.gd" id="1_o33o4"]
+[ext_resource type="Texture2D" uid="uid://c1eqf1cse1hch" path="res://addons/maaacks_game_template/base/assets/remapping_input_icons/addition_symbol.png" id="2_ppi0j"]
+[ext_resource type="Texture2D" uid="uid://bteq3ica74h30" path="res://addons/maaacks_game_template/base/assets/remapping_input_icons/subtraction_symbol.png" id="3_hb3xh"]
+
+[node name="InputActionsTree" type="Tree" unique_id=1082851938]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+hide_root = true
+script = ExtResource("1_o33o4")
+input_action_names = Array[StringName]([&"move_forward", &"move_backward", &"move_up", &"move_down", &"move_left", &"move_right", &"interact"])
+readable_action_names = Array[String](["Move Forward", "Move Backward", "Move Up", "Move Down", "Move Left", "Move Right", "Interact"])
+add_button_texture = ExtResource("2_ppi0j")
+remove_button_texture = ExtResource("3_hb3xh")
+action_name_map = {
+&"interact": "Interact",
+&"move_backward": "Move Backward",
+&"move_down": "Move Down",
+&"move_forward": "Move Forward",
+&"move_left": "Move Left",
+&"move_right": "Move Right",
+&"move_up": "Move Up"
+}
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd
new file mode 100644
index 0000000..d186536
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd
@@ -0,0 +1,140 @@
+@tool
+class_name InputIconMapper
+extends FileLister
+
+signal joypad_device_changed
+
+const COMMON_REPLACE_STRINGS: Dictionary = {
+ "L 1": "Left Shoulder",
+ "R 1": "Right Shoulder",
+ "L 2": "Left Trigger",
+ "R 2": "Right Trigger",
+ "Lt": "Left Trigger",
+ "Rt": "Right Trigger",
+ "Lb": "Left Shoulder",
+ "Rb": "Right Shoulder",
+} # Dictionary[String, String]
+## Gives priority to icons with occurrences of the provided strings.
+@export var prioritized_strings : Array[String]
+## Replaces the first occurence in icon names of the key with the value.
+@export var replace_strings : Dictionary # Dictionary[String, String]
+## Filters the icon names of the provided strings.
+@export var filtered_strings : Array[String]
+## Adds entries for "Up", "Down", "Left", "Right" to icon names ending with "Stick".
+@export var add_stick_directions : bool = false
+@export var intial_joypad_device : String = InputEventHelper.DEVICE_GENERIC
+## Attempt to match the icon names to the input names based on the string rules.
+@export var _match_icons_to_inputs_action : bool = false :
+ set(value):
+ if value and Engine.is_editor_hint():
+ _match_icons_to_inputs()
+# For Godot 4.4
+# @export_tool_button("Match Icons to Inputs") var _match_icons_to_inputs_action = _match_icons_to_inputs
+@export var matching_icons : Dictionary # Dictionary[String, Texture]
+@export_group("Debug")
+@export var all_icons : Dictionary # Dictionary[String, Texture]
+
+@onready var last_joypad_device = intial_joypad_device
+
+func _is_end_of_word(full_string : String, what : String) -> bool:
+ var string_end_position = full_string.find(what) + what.length()
+ var end_of_word : bool
+ if string_end_position + 1 < full_string.length():
+ var next_character = full_string.substr(string_end_position, 1)
+ end_of_word = next_character == " "
+ return full_string.ends_with(what) or end_of_word
+
+func _get_standard_joy_name(joy_name : String) -> String:
+ var all_replace_strings := replace_strings.duplicate()
+ all_replace_strings.merge(COMMON_REPLACE_STRINGS)
+ for what in all_replace_strings:
+ if joy_name.contains(what) and _is_end_of_word(joy_name, what):
+ var position = joy_name.find(what)
+ joy_name = joy_name.erase(position, what.length())
+ joy_name = joy_name.insert(position, all_replace_strings[what])
+ var combined_joystick_name : Array[String] = []
+ for part in joy_name.split(" "):
+ if part.to_lower() in filtered_strings:
+ continue
+ if not part.is_empty():
+ combined_joystick_name.append(part)
+ joy_name = " ".join(combined_joystick_name)
+ joy_name = joy_name.strip_edges()
+ return joy_name
+
+func _match_icon_to_file(file : String) -> void:
+ var matching_string : String = file.get_file().get_basename()
+ var icon : Texture = load(file)
+ if not icon:
+ return
+ all_icons[matching_string] = icon
+ matching_string = matching_string.capitalize()
+ matching_string = _get_standard_joy_name(matching_string)
+ matching_string = matching_string.strip_edges()
+ if add_stick_directions and matching_string.ends_with("Stick"):
+ matching_icons[matching_string + " Up"] = icon
+ matching_icons[matching_string + " Down"] = icon
+ matching_icons[matching_string + " Left"] = icon
+ matching_icons[matching_string + " Right"] = icon
+ return
+ if matching_string in matching_icons:
+ return
+ matching_icons[matching_string] = icon
+
+func _prioritized_files() -> Array[String]:
+ var priority_levels : Dictionary # Dictionary[String, int]
+ var priortized_files : Array[String]
+ for prioritized_string in prioritized_strings:
+ for file in files:
+ if file.containsn(prioritized_string):
+ if file in priority_levels:
+ priority_levels[file] += 1
+ else:
+ priority_levels[file] = 1
+ var priority_file_map : Dictionary # Dictionary[int, Array]
+ var max_priority_level : int = 0
+ for file in priority_levels:
+ var priority_level = priority_levels[file]
+ max_priority_level = max(priority_level, max_priority_level)
+ if priority_level in priority_file_map:
+ priority_file_map[priority_level].append(file)
+ else:
+ priority_file_map[priority_level] = [file]
+ while max_priority_level > 0:
+ for priority_file in priority_file_map[max_priority_level]:
+ priortized_files.append(priority_file)
+ max_priority_level -= 1
+ return priortized_files
+
+func _match_icons_to_inputs() -> void:
+ matching_icons.clear()
+ all_icons.clear()
+ for prioritized_file in _prioritized_files():
+ _match_icon_to_file(prioritized_file)
+ for file in files:
+ _match_icon_to_file(file)
+
+func get_icon(input_event : InputEvent) -> Texture:
+ var specific_text = InputEventHelper.get_device_specific_text(input_event, last_joypad_device)
+ if specific_text in matching_icons:
+ return matching_icons[specific_text]
+ return null
+
+func _assign_joypad_0_to_last() -> void:
+ if last_joypad_device != intial_joypad_device : return
+ var connected_joypads := Input.get_connected_joypads()
+ if connected_joypads.is_empty(): return
+ last_joypad_device = InputEventHelper.get_device_name_by_id(connected_joypads[0])
+
+func _input(event : InputEvent) -> void:
+ var device_name = InputEventHelper.get_device_name(event)
+ if device_name != InputEventHelper.DEVICE_GENERIC and device_name != last_joypad_device:
+ last_joypad_device = device_name
+ joypad_device_changed.emit()
+
+func _ready() -> void:
+ _assign_joypad_0_to_last()
+ if files.size() == 0:
+ _refresh_files()
+ if matching_icons.size() == 0:
+ _match_icons_to_inputs()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd.uid
new file mode 100644
index 0000000..4a8d67b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd.uid
@@ -0,0 +1 @@
+uid://cqigj1uumknrp
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.tscn
new file mode 100644
index 0000000..75e07d3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.tscn
@@ -0,0 +1,6 @@
+[gd_scene format=3 uid="uid://qoexj4ptqt8a"]
+
+[ext_resource type="Script" uid="uid://cqigj1uumknrp" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.gd" id="1_msrpt"]
+
+[node name="InputIconMapper" type="Node" unique_id=338050523]
+script = ExtResource("1_msrpt")
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd
new file mode 100644
index 0000000..5c650c9
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd
@@ -0,0 +1,92 @@
+@tool
+extends Control
+
+const ALREADY_ASSIGNED_TEXT : String = "{key} already assigned to {action}."
+const ONE_INPUT_MINIMUM_TEXT : String = "%s must have at least one key or button assigned."
+const KEY_DELETION_TEXT : String = "Are you sure you want to remove {key} from {action}?"
+
+@export_enum("List", "Tree") var remapping_mode : int = 0 :
+ set(value):
+ remapping_mode = value
+ if is_inside_tree():
+ match(remapping_mode):
+ 0:
+ %InputActionsList.show()
+ %InputActionsTree.hide()
+ 1:
+ %InputActionsList.hide()
+ %InputActionsTree.show()
+
+@onready var assignment_placeholder_text = $KeyAssignmentWindow.text
+
+var last_input_readable_name
+
+func _ready() -> void:
+ remapping_mode = remapping_mode
+
+func _add_action_event() -> void:
+ var last_input_event = $KeyAssignmentWindow.last_input_event
+ last_input_readable_name = $KeyAssignmentWindow.last_input_text
+ match(remapping_mode):
+ 0:
+ %InputActionsList.add_action_event(last_input_readable_name, last_input_event)
+ 1:
+ %InputActionsTree.add_action_event(last_input_readable_name, last_input_event)
+
+func _remove_action_event(item : TreeItem) -> void:
+ %InputActionsTree.remove_action_event(item)
+
+func _on_reset_button_pressed() -> void:
+ $ResetConfirmation.show()
+
+func _on_key_deletion_confirmation_confirmed() -> void:
+ var editing_item = %InputActionsTree.editing_item
+ if is_instance_valid(editing_item):
+ _remove_action_event(editing_item)
+
+func _on_key_assignment_window_confirmed() -> void:
+ _add_action_event()
+
+func _open_key_assignment_window(action_name : String, readable_input_name : String = assignment_placeholder_text) -> void:
+ $KeyAssignmentWindow.title = tr("Assign Key for {action}").format({action = action_name})
+ $KeyAssignmentWindow.text = readable_input_name
+ $KeyAssignmentWindow.confirm_button.disabled = true
+ $KeyAssignmentWindow.show()
+
+func _on_input_actions_tree_add_button_clicked(action_name) -> void:
+ _open_key_assignment_window(action_name)
+
+func _on_input_actions_tree_remove_button_clicked(action_name, input_name) -> void:
+ $KeyDeletionConfirmation.title = tr("Remove Key for {action}").format({action = action_name})
+ $KeyDeletionConfirmation.text = tr(KEY_DELETION_TEXT).format({key = input_name, action = action_name})
+ $KeyDeletionConfirmation.show()
+
+func _popup_already_assigned(action_name, input_name) -> void:
+ $AlreadyAssignedMessage.text = tr(ALREADY_ASSIGNED_TEXT).format({key = input_name, action = action_name})
+ $AlreadyAssignedMessage.show()
+
+func _popup_minimum_reached(action_name : String) -> void:
+ $OneInputMinimumMessage.text = ONE_INPUT_MINIMUM_TEXT % action_name
+ $OneInputMinimumMessage.show()
+
+func _on_input_actions_tree_already_assigned(action_name, input_name) -> void:
+ _popup_already_assigned.call_deferred(action_name, input_name)
+
+func _on_input_actions_tree_minimum_reached(action_name) -> void:
+ _popup_minimum_reached.call_deferred(action_name)
+
+func _on_input_actions_list_already_assigned(action_name, input_name) -> void:
+ _popup_already_assigned.call_deferred(action_name, input_name)
+
+func _on_input_actions_list_minimum_reached(action_name) -> void:
+ _popup_minimum_reached.call_deferred(action_name)
+
+func _on_input_actions_list_button_clicked(action_name, readable_input_name) -> void:
+ _open_key_assignment_window(action_name, readable_input_name)
+
+func _on_reset_confirmation_confirmed() -> void:
+ match(remapping_mode):
+ 0:
+ %InputActionsList.reset()
+ 1:
+ %InputActionsTree.reset()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd.uid
new file mode 100644
index 0000000..72cfa78
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd.uid
@@ -0,0 +1 @@
+uid://eborw7q4b07h
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.tscn
new file mode 100644
index 0000000..692f991
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.tscn
@@ -0,0 +1,100 @@
+[gd_scene format=3 uid="uid://dp3rgqaehb3xu"]
+
+[ext_resource type="Script" uid="uid://eborw7q4b07h" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.gd" id="1"]
+[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_wft4x"]
+[ext_resource type="PackedScene" uid="uid://bxp45814v6ydv" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_list.tscn" id="4_lf2nw"]
+[ext_resource type="PackedScene" uid="uid://ci6wgl2ngd35n" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_actions_tree.tscn" id="5_b2whh"]
+[ext_resource type="PackedScene" uid="uid://cwt4p3bufkke5" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="7_5j1ya"]
+[ext_resource type="PackedScene" uid="uid://dgravx3vt5g3i" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.tscn" id="7_r3r3g"]
+[ext_resource type="PackedScene" uid="uid://6gdbfi0172ji" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="8_jtpjy"]
+
+[node name="Controls" type="MarginContainer" unique_id=1417083348]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/margin_left = 32
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 32
+theme_override_constants/margin_bottom = 8
+script = ExtResource("1")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=539297323]
+layout_mode = 2
+script = ExtResource("2_wft4x")
+search_depth = 5
+
+[node name="InputMappingContainer" type="VBoxContainer" parent="VBoxContainer" unique_id=1004992485]
+layout_mode = 2
+size_flags_vertical = 3
+alignment = 1
+
+[node name="Label" type="Label" parent="VBoxContainer/InputMappingContainer" unique_id=1142261578]
+layout_mode = 2
+text = "Actions & Inputs"
+horizontal_alignment = 1
+
+[node name="InputActionsList" parent="VBoxContainer/InputMappingContainer" unique_id=2089976301 instance=ExtResource("4_lf2nw")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="InputActionsTree" parent="VBoxContainer/InputMappingContainer" unique_id=780755880 instance=ExtResource("5_b2whh")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/InputMappingContainer" unique_id=668442458]
+layout_mode = 2
+alignment = 1
+
+[node name="ResetButton" type="Button" parent="VBoxContainer/InputMappingContainer/HBoxContainer" unique_id=14998312]
+layout_mode = 2
+text = "Reset"
+
+[node name="KeyDeletionConfirmation" parent="." unique_id=1120617858 instance=ExtResource("7_5j1ya")]
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 2
+text = "Are you sure you want to remove KEY from ACTION?"
+title = "Remove Key"
+
+[node name="ResetConfirmation" parent="." unique_id=1967398625 instance=ExtResource("7_5j1ya")]
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 2
+text = "Are you sure you want to reset controls back to the defaults?"
+title = "Reset to Default"
+
+[node name="OneInputMinimumMessage" parent="." unique_id=1110523665 instance=ExtResource("8_jtpjy")]
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 2
+update_content = true
+title = "One Input Minimum"
+
+[node name="AlreadyAssignedMessage" parent="." unique_id=574954440 instance=ExtResource("8_jtpjy")]
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 2
+update_content = true
+title = "Already Assigned"
+
+[node name="KeyAssignmentWindow" parent="." unique_id=751125317 instance=ExtResource("7_r3r3g")]
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 2
+
+[connection signal="already_assigned" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_already_assigned"]
+[connection signal="button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_button_clicked"]
+[connection signal="minimum_reached" from="VBoxContainer/InputMappingContainer/InputActionsList" to="." method="_on_input_actions_list_minimum_reached"]
+[connection signal="add_button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_add_button_clicked"]
+[connection signal="already_assigned" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_already_assigned"]
+[connection signal="minimum_reached" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_minimum_reached"]
+[connection signal="remove_button_clicked" from="VBoxContainer/InputMappingContainer/InputActionsTree" to="." method="_on_input_actions_tree_remove_button_clicked"]
+[connection signal="pressed" from="VBoxContainer/InputMappingContainer/HBoxContainer/ResetButton" to="." method="_on_reset_button_pressed"]
+[connection signal="confirmed" from="KeyDeletionConfirmation" to="." method="_on_key_deletion_confirmation_confirmed"]
+[connection signal="confirmed" from="ResetConfirmation" to="." method="_on_reset_confirmation_confirmed"]
+[connection signal="confirmed" from="KeyAssignmentWindow" to="." method="_on_key_assignment_window_confirmed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd
new file mode 100644
index 0000000..bc0cca9
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd
@@ -0,0 +1,112 @@
+@tool
+extends ConfirmationOverlaidWindow
+## Scene to confirm a new input for an action name.
+
+const LISTENING_TEXT : String = "Listening for input..."
+const FOCUS_HERE_TEXT : String = "Focus here to assign inputs."
+const CONFIRM_INPUT_TEXT : String = "Press again to confirm..."
+const NO_INPUT_TEXT : String = "None"
+
+enum InputConfirmation {
+ SINGLE,
+ DOUBLE,
+ OK_BUTTON
+}
+## Confirmations required before a new input is accepted for an aciton.
+@export var input_confirmation : InputConfirmation = InputConfirmation.SINGLE
+
+var last_input_event : InputEvent
+var last_input_text : String
+var listening : bool = false
+var confirming : bool = false
+
+func _record_input_event(event : InputEvent) -> void:
+ last_input_text = InputEventHelper.get_text(event)
+ if last_input_text.is_empty():
+ return
+ last_input_event = event
+ %InputLabel.text = last_input_text
+ confirm_button.disabled = false
+
+func _is_recordable_input(event : InputEvent) -> bool:
+ return event != null and \
+ (event is InputEventKey or \
+ event is InputEventMouseButton or \
+ event is InputEventJoypadButton or \
+ (event is InputEventJoypadMotion and \
+ abs(event.axis_value) > 0.5)) and \
+ event.is_pressed()
+
+func _start_listening() -> void:
+ %InputTextEdit.placeholder_text = LISTENING_TEXT
+ listening = true
+ %DelayTimer.start()
+
+func _stop_listening() -> void:
+ %InputTextEdit.placeholder_text = FOCUS_HERE_TEXT
+ listening = false
+ confirming = false
+
+func _on_input_text_edit_focus_entered() -> void:
+ _start_listening.call_deferred()
+
+func _on_input_text_edit_focus_exited() -> void:
+ _stop_listening()
+
+func _focus_on_ok() -> void:
+ confirm_button.grab_focus()
+
+func _ready() -> void:
+ confirm_button.focus_neighbor_top = ^"../../../BodyMargin/VBoxContainer/InputTextEdit"
+ close_button.focus_neighbor_top = ^"../../../BodyMargin/VBoxContainer/InputTextEdit"
+ super._ready()
+
+func _input_matches_last(event : InputEvent) -> bool:
+ return last_input_text == InputEventHelper.get_text(event)
+
+func _is_mouse_input(event : InputEvent) -> bool:
+ return event is InputEventMouse
+
+func _input_confirms_choice(event : InputEvent) -> bool:
+ return confirming and not _is_mouse_input(event) and _input_matches_last(event)
+
+func _should_process_input_event(event : InputEvent) -> bool:
+ return listening and _is_recordable_input(event) and %DelayTimer.is_stopped()
+
+func _should_confirm_input_event(event : InputEvent) -> bool:
+ return not _is_mouse_input(event)
+
+func _confirm_choice() -> void:
+ confirmed.emit()
+ close()
+
+func _process_input_event(event : InputEvent) -> void:
+ if not _should_process_input_event(event):
+ return
+ if _input_confirms_choice(event):
+ confirming = false
+ if input_confirmation == InputConfirmation.DOUBLE:
+ _confirm_choice()
+ else:
+ _focus_on_ok.call_deferred()
+ return
+ _record_input_event(event)
+ if input_confirmation == InputConfirmation.SINGLE:
+ _confirm_choice()
+ if _should_confirm_input_event(event):
+ confirming = true
+ %DelayTimer.start()
+ %InputTextEdit.placeholder_text = CONFIRM_INPUT_TEXT
+
+func _on_input_text_edit_gui_input(event) -> void:
+ %InputTextEdit.set_deferred("text", "")
+ _process_input_event(event)
+
+func _on_visibility_changed() -> void:
+ super._on_visibility_changed()
+ if visible:
+ if not text.strip_edges().is_empty():
+ %InputLabel.text = text
+ else:
+ %InputLabel.text = NO_INPUT_TEXT
+ %InputTextEdit.grab_focus()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd.uid
new file mode 100644
index 0000000..b34806b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd.uid
@@ -0,0 +1 @@
+uid://custha7r0uoic
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.tscn
new file mode 100644
index 0000000..c47c6f3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.tscn
@@ -0,0 +1,64 @@
+[gd_scene format=3 uid="uid://dgravx3vt5g3i"]
+
+[ext_resource type="PackedScene" uid="uid://cwt4p3bufkke5" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="1_6c67a"]
+[ext_resource type="Script" uid="uid://custha7r0uoic" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/key_assignment_window.gd" id="2_oif0q"]
+
+[node name="KeyAssignmentWindow" unique_id=1194922714 instance=ExtResource("1_6c67a")]
+offset_left = -210.0
+offset_top = -100.0
+offset_right = 210.0
+offset_bottom = 100.0
+script = ExtResource("2_oif0q")
+input_confirmation = 0
+close_button_text = "Close"
+title = "Set Input"
+
+[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(1788474031) index="0" unique_id=1049966061]
+text = "Set Input"
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+visible = false
+
+[node name="VBoxContainer" type="VBoxContainer" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="1" unique_id=1136766756]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="InputLabel" type="Label" parent="ContentContainer/BoxContainer/BodyMargin/VBoxContainer" index="0" unique_id=1464176867]
+unique_name_in_owner = true
+layout_mode = 2
+text = "None"
+horizontal_alignment = 1
+
+[node name="InputTextEdit" type="TextEdit" parent="ContentContainer/BoxContainer/BodyMargin/VBoxContainer" index="1" unique_id=1475588538]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+placeholder_text = "Focus here to assign inputs."
+context_menu_enabled = false
+shortcut_keys_enabled = false
+selecting_enabled = false
+deselect_on_focus_loss_enabled = false
+drag_and_drop_selection_enabled = false
+middle_mouse_paste_enabled = false
+caret_move_on_right_click = false
+
+[node name="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" parent_id_path=PackedInt32Array(1413292752) index="0" unique_id=1371114575]
+null_focus_enabled = false
+joypad_enabled = false
+mouse_hidden_enabled = false
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0" unique_id=314526102]
+focus_neighbor_top = NodePath("../../../BodyMargin/VBoxContainer/InputTextEdit")
+text = "Close"
+
+[node name="ConfirmButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1" unique_id=1052970550]
+focus_neighbor_top = NodePath("../../../BodyMargin/VBoxContainer/InputTextEdit")
+
+[node name="DelayTimer" type="Timer" parent="." index="1" unique_id=1825912432]
+unique_name_in_owner = true
+wait_time = 0.1
+one_shot = true
+
+[connection signal="focus_entered" from="ContentContainer/BoxContainer/BodyMargin/VBoxContainer/InputTextEdit" to="." method="_on_input_text_edit_focus_entered"]
+[connection signal="focus_exited" from="ContentContainer/BoxContainer/BodyMargin/VBoxContainer/InputTextEdit" to="." method="_on_input_text_edit_focus_exited"]
+[connection signal="gui_input" from="ContentContainer/BoxContainer/BodyMargin/VBoxContainer/InputTextEdit" to="." method="_on_input_text_edit_gui_input"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd
new file mode 100644
index 0000000..c47d5df
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd
@@ -0,0 +1,46 @@
+extends Control
+
+@onready var mute_control = %MuteControl
+@onready var fullscreen_control = %FullscreenControl
+
+## Scene for adjusting the volume of the audio busses.
+@export var audio_control_scene : PackedScene
+## Optional names of audio busses that should be ignored.
+@export var hide_busses : Array[String]
+
+func _on_bus_changed(bus_value : float, bus_iter : int) -> void:
+ AppSettings.set_bus_volume(bus_iter, bus_value)
+
+func _add_audio_control(bus_name : String, bus_value : float, bus_iter : int) -> void:
+ if audio_control_scene == null or bus_name in hide_busses or bus_name.begins_with(AppSettings.SYSTEM_BUS_NAME_PREFIX):
+ return
+ var audio_control = audio_control_scene.instantiate()
+ %AudioControlContainer.call_deferred("add_child", audio_control)
+ if audio_control is OptionControl:
+ audio_control.option_section = OptionControl.OptionSections.AUDIO
+ audio_control.option_name = bus_name
+ audio_control.value = bus_value
+ audio_control.connect("setting_changed", _on_bus_changed.bind(bus_iter))
+
+func _add_audio_bus_controls() -> void:
+ for bus_iter in AudioServer.bus_count:
+ var bus_name : String = AppSettings.get_audio_bus_name(bus_iter)
+ var linear : float = AppSettings.get_bus_volume(bus_iter)
+ _add_audio_control(bus_name, linear, bus_iter)
+
+func _update_ui() -> void:
+ _add_audio_bus_controls()
+ mute_control.value = AppSettings.is_muted()
+ fullscreen_control.value = AppSettings.is_fullscreen(get_window())
+
+func _sync_with_config() -> void:
+ _update_ui()
+
+func _ready() -> void:
+ _sync_with_config()
+
+func _on_mute_control_setting_changed(value : bool) -> void:
+ AppSettings.set_mute(value)
+
+func _on_fullscreen_control_setting_changed(value : bool) -> void:
+ AppSettings.set_fullscreen_enabled(value, get_window())
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd.uid
new file mode 100644
index 0000000..926a13c
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd.uid
@@ -0,0 +1 @@
+uid://c0jjk82iuuyh3
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd
new file mode 100644
index 0000000..c6a9b7a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd
@@ -0,0 +1,84 @@
+@tool
+class_name ListOptionControl
+extends OptionControl
+
+## Locks Option Titles from auto-updating when editing Option Values.
+## Intentionally put first for initialization.
+@export var lock_titles : bool = false
+## Defines the list of possible values for the variable
+## this option stores in the config file.
+@export var option_values : Array :
+ set(value) :
+ option_values = value
+ _on_option_values_changed()
+
+## Defines the list of options displayed to the user.
+## Length should match with Option Values.
+@export var option_titles : Array[String] :
+ set(value):
+ option_titles = value
+ if is_inside_tree():
+ _set_option_list(option_titles)
+
+var custom_option_values : Array
+
+func _on_option_values_changed() -> void:
+ if option_values.is_empty(): return
+ custom_option_values = option_values.duplicate()
+ var first_value = custom_option_values.front()
+ property_type = typeof(first_value)
+ _set_titles_from_values()
+
+func _on_setting_changed(value : Variant) -> void:
+ if value < custom_option_values.size() and value >= 0:
+ super._on_setting_changed(custom_option_values[value])
+
+func _set_titles_from_values() -> void:
+ if lock_titles: return
+ var mapped_titles : Array[String] = []
+ for option_value in custom_option_values:
+ mapped_titles.append(_value_title_map(option_value))
+ option_titles = mapped_titles
+
+func _value_title_map(value : Variant) -> String:
+ return "%s" % value
+
+func _match_value_to_other(value : Variant, other : Variant) -> Variant:
+ # Primarily for when the editor saves floats as ints instead
+ if value is int and other is float:
+ return float(value)
+ if value is float and other is int:
+ return int(round(value))
+ return value
+
+func _refresh_option_values(value : Variant) -> void:
+ if option_values.is_empty(): return
+ if value == null:
+ return
+ custom_option_values = option_values.duplicate()
+ value = _match_value_to_other(value, custom_option_values.front())
+ if value not in custom_option_values and typeof(value) == property_type:
+ custom_option_values.append(value)
+ custom_option_values.sort()
+ _set_titles_from_values()
+ if value not in option_values:
+ disable_option(custom_option_values.find(value))
+
+func set_value(value : Variant) -> void:
+ _refresh_option_values(value)
+ value = custom_option_values.find(value)
+ super.set_value(value)
+
+func _set_option_list(option_titles_list : Array) -> void:
+ %OptionButton.clear()
+ for option_title in option_titles_list:
+ %OptionButton.add_item(option_title)
+
+func disable_option(option_index : int, disabled : bool = true) -> void:
+ %OptionButton.set_item_disabled(option_index, disabled)
+
+func _ready() -> void:
+ lock_titles = lock_titles
+ option_titles = option_titles
+ option_values = option_values
+ super._ready()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd.uid
new file mode 100644
index 0000000..69ab4eb
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd.uid
@@ -0,0 +1 @@
+uid://b8xqufg4re3c2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn
new file mode 100644
index 0000000..a77680e
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn
@@ -0,0 +1,14 @@
+[gd_scene format=3 uid="uid://b6bl3n5mp3m1e"]
+
+[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.tscn" id="1_blo3b"]
+[ext_resource type="Script" uid="uid://b8xqufg4re3c2" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.gd" id="2_kt4vl"]
+
+[node name="OptionControl" unique_id=16284350 instance=ExtResource("1_blo3b")]
+script = ExtResource("2_kt4vl")
+lock_titles = false
+option_values = []
+option_titles = []
+
+[node name="OptionButton" type="OptionButton" parent="." index="1" unique_id=264509485]
+unique_name_in_owner = true
+layout_mode = 2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd
new file mode 100644
index 0000000..231ee21
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd
@@ -0,0 +1,136 @@
+@tool
+class_name OptionControl
+extends Control
+## Generic scene for editing a value of the [PlayerConfig].
+
+signal setting_changed(value)
+
+enum OptionSections{
+ NONE,
+ INPUT,
+ AUDIO,
+ VIDEO,
+ GAME,
+ APPLICATION,
+ CUSTOM,
+}
+
+const OptionSectionNames : Dictionary = {
+ OptionSections.NONE : "",
+ OptionSections.INPUT : AppSettings.INPUT_SECTION,
+ OptionSections.AUDIO : AppSettings.AUDIO_SECTION,
+ OptionSections.VIDEO : AppSettings.VIDEO_SECTION,
+ OptionSections.GAME : AppSettings.GAME_SECTION,
+ OptionSections.APPLICATION : AppSettings.APPLICATION_SECTION,
+ OptionSections.CUSTOM : AppSettings.CUSTOM_SECTION,
+}
+
+## Locks config names in case of issues with inherited scenes.
+## Intentionally put first for initialization.
+@export var lock_config_names : bool = false
+## Defines text displayed to the user.
+@export var option_name : String :
+ set(value):
+ var _update_config : bool = option_name.to_pascal_case() == key and not lock_config_names
+ option_name = value
+ if is_inside_tree():
+ %OptionLabel.text = "%s%s" % [option_name, label_suffix]
+ if _update_config:
+ key = option_name.to_pascal_case()
+## Defines what section in the config file this option belongs under.
+@export var option_section : OptionSections :
+ set(value):
+ var _update_config : bool = OptionSectionNames[option_section] == section and not lock_config_names
+ option_section = value
+ if _update_config:
+ section = OptionSectionNames[option_section]
+
+@export_group("Config Names")
+## Defines the key for this option variable in the config file.
+@export var key : String
+## Defines the section for this option variable in the config file.
+@export var section : String
+@export_group("Format")
+@export var label_suffix : String = " :"
+@export_group("Properties")
+## Defines whether the option is editable, or only visible by the user.
+@export var editable : bool = true : set = set_editable
+## Defines what kind of variable this option stores in the config file.
+@export var property_type : Variant.Type = TYPE_BOOL
+
+## It is advised to use an external editor to set the default value in the scene file.
+## Godot can experience a bug (caching issue?) that may undo changes.
+var default_value
+var _connected_nodes : Array
+
+func _on_setting_changed(value) -> void:
+ if Engine.is_editor_hint(): return
+ PlayerConfig.set_config(section, key, value)
+ setting_changed.emit(value)
+
+func _get_setting(default : Variant = null) -> Variant:
+ return PlayerConfig.get_config(section, key, default)
+
+func _connect_option_inputs(node) -> void:
+ if node in _connected_nodes: return
+ if node is Button:
+ if node is OptionButton:
+ node.item_selected.connect(_on_setting_changed)
+ elif node is ColorPickerButton:
+ node.color_changed.connect(_on_setting_changed)
+ else:
+ node.toggled.connect(_on_setting_changed)
+ _connected_nodes.append(node)
+ if node is Range:
+ node.value_changed.connect(_on_setting_changed)
+ _connected_nodes.append(node)
+ if node is LineEdit or node is TextEdit:
+ node.text_changed.connect(_on_setting_changed)
+ _connected_nodes.append(node)
+
+func set_value(value : Variant) -> void:
+ if value == null:
+ return
+ for node in get_children():
+ if node is Button:
+ if node is OptionButton:
+ node.select(value as int)
+ elif node is ColorPickerButton:
+ node.color = value as Color
+ else:
+ node.button_pressed = value as bool
+ if node is Range:
+ node.value = value as float
+ if node is LineEdit or node is TextEdit:
+ node.text = "%s" % value
+
+func set_editable(value : bool = true) -> void:
+ editable = value
+ for node in get_children():
+ if node is Button:
+ node.disabled = !editable
+ if node is Slider or node is SpinBox or node is LineEdit or node is TextEdit:
+ node.editable = editable
+
+func _ready() -> void:
+ lock_config_names = lock_config_names
+ option_section = option_section
+ option_name = option_name
+ property_type = property_type
+ default_value = default_value
+ set_value(_get_setting(default_value))
+ for child in get_children():
+ _connect_option_inputs(child)
+ child_entered_tree.connect(_connect_option_inputs)
+
+func _set(property : StringName, value : Variant) -> bool:
+ if property == "value":
+ set_value(value)
+ return true
+ return false
+
+func _get_property_list() -> Array[Dictionary]:
+ return [
+ { "name": "value", "type": property_type, "usage": PROPERTY_USAGE_NONE},
+ { "name": "default_value", "type": property_type}
+ ]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd.uid
new file mode 100644
index 0000000..54d3316
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd.uid
@@ -0,0 +1 @@
+uid://cafqki2b08kwu
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.tscn
new file mode 100644
index 0000000..ed29323
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.tscn
@@ -0,0 +1,17 @@
+[gd_scene format=3 uid="uid://d7te75il06t7"]
+
+[ext_resource type="Script" uid="uid://cafqki2b08kwu" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.gd" id="1_jvl5q"]
+
+[node name="OptionControl" type="HBoxContainer" unique_id=364203356]
+custom_minimum_size = Vector2(0, 40)
+offset_right = 400.0
+offset_bottom = 40.0
+script = ExtResource("1_jvl5q")
+default_value = false
+
+[node name="OptionLabel" type="Label" parent="." unique_id=1854788461]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = " :"
+vertical_alignment = 1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn
new file mode 100644
index 0000000..d75295a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn
@@ -0,0 +1,19 @@
+[gd_scene format=3 uid="uid://cl416gdb1fgwr"]
+
+[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.tscn" id="1_16hlr"]
+
+[node name="OptionControl" unique_id=1557081957 instance=ExtResource("1_16hlr")]
+custom_minimum_size = Vector2(0, 28)
+offset_bottom = 28.0
+property_type = 3
+default_value = 1.0
+
+[node name="HSlider" type="HSlider" parent="." index="1" unique_id=424108384]
+custom_minimum_size = Vector2(256, 0)
+layout_mode = 2
+size_flags_vertical = 4
+max_value = 1.0
+step = 0.05
+value = 1.0
+tick_count = 11
+ticks_on_borders = true
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn
new file mode 100644
index 0000000..6c6b13c
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn
@@ -0,0 +1,8 @@
+[gd_scene format=3 uid="uid://bsxh6v7j0257h"]
+
+[ext_resource type="PackedScene" uid="uid://d7te75il06t7" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/option_control.tscn" id="1_8rnmo"]
+
+[node name="OptionControl" unique_id=1563851297 instance=ExtResource("1_8rnmo")]
+
+[node name="CheckButton" type="CheckButton" parent="." index="1" unique_id=1452268147]
+layout_mode = 2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd
new file mode 100644
index 0000000..562d2e3
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd
@@ -0,0 +1,9 @@
+@tool
+class_name Vector2ListOptionControl
+extends ListOptionControl
+
+func _value_title_map(value : Variant) -> String:
+ if value is Vector2 or value is Vector2i:
+ return "%d x %d" % [value.x , value.y]
+ else:
+ return super._value_title_map(value)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd.uid
new file mode 100644
index 0000000..562da78
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd.uid
@@ -0,0 +1 @@
+uid://brntdgf3sv0s0
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.tscn
new file mode 100644
index 0000000..1eb3968
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.tscn
@@ -0,0 +1,7 @@
+[gd_scene format=3 uid="uid://c01ayjblhcg1t"]
+
+[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn" id="1_jqwiw"]
+[ext_resource type="Script" uid="uid://brntdgf3sv0s0" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.gd" id="2_w33vs"]
+
+[node name="OptionControl" unique_id=21057374 instance=ExtResource("1_jqwiw")]
+script = ExtResource("2_w33vs")
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd
new file mode 100644
index 0000000..ace973b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd
@@ -0,0 +1,13 @@
+extends TabContainer
+## Applies UI page up and page down inputs to tab switching.
+
+func _unhandled_input(event : InputEvent) -> void:
+ if not is_visible_in_tree():
+ return
+ if event.is_action_pressed("ui_page_down"):
+ current_tab = (current_tab+1) % get_tab_count()
+ elif event.is_action_pressed("ui_page_up"):
+ if current_tab == 0:
+ current_tab = get_tab_count()-1
+ else:
+ current_tab = current_tab-1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd.uid
new file mode 100644
index 0000000..01c0cbd
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd.uid
@@ -0,0 +1 @@
+uid://c3mignmhuvvq4
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd
new file mode 100644
index 0000000..298d53b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd
@@ -0,0 +1,37 @@
+extends Control
+
+func _preselect_resolution(window : Window) -> void:
+ %ResolutionControl.value = window.size
+
+func _update_resolution_options_enabled(window : Window) -> void:
+ if OS.has_feature("web"):
+ %ResolutionControl.editable = false
+ %ResolutionControl.tooltip_text = "Disabled for web"
+ elif AppSettings.is_fullscreen(window):
+ %ResolutionControl.editable = false
+ %ResolutionControl.tooltip_text = "Disabled for fullscreen"
+ else:
+ %ResolutionControl.editable = true
+ %ResolutionControl.tooltip_text = "Select a screen size"
+
+func _update_ui(window : Window) -> void:
+ %FullscreenControl.value = AppSettings.is_fullscreen(window)
+ _preselect_resolution(window)
+ %VSyncControl.value = AppSettings.get_vsync(window)
+ _update_resolution_options_enabled(window)
+
+func _ready() -> void:
+ var window : Window = get_window()
+ _update_ui(window)
+ window.connect("size_changed", _preselect_resolution.bind(window))
+
+func _on_fullscreen_control_setting_changed(value) -> void:
+ var window : Window = get_window()
+ AppSettings.set_fullscreen_enabled(value, window)
+ _update_resolution_options_enabled(window)
+
+func _on_resolution_control_setting_changed(value) -> void:
+ AppSettings.set_resolution(value, get_window(), false)
+
+func _on_v_sync_control_setting_changed(value) -> void:
+ AppSettings.set_vsync(value, get_window())
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd.uid
new file mode 100644
index 0000000..77495f0
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd.uid
@@ -0,0 +1 @@
+uid://cpe5r24151r5n
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.tscn
new file mode 100644
index 0000000..9bb06a8
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.tscn
@@ -0,0 +1,60 @@
+[gd_scene format=3 uid="uid://b2numvphf2kau"]
+
+[ext_resource type="Script" uid="uid://cpe5r24151r5n" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.gd" id="1"]
+[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_dgrai"]
+[ext_resource type="PackedScene" uid="uid://bsxh6v7j0257h" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn" id="3_uded6"]
+[ext_resource type="PackedScene" uid="uid://c01ayjblhcg1t" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/vector_2_list_option_control.tscn" id="4_gwtfq"]
+[ext_resource type="PackedScene" uid="uid://b6bl3n5mp3m1e" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn" id="5_881de"]
+
+[node name="Video" type="MarginContainer" unique_id=1604782575]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+theme_override_constants/margin_top = 24
+theme_override_constants/margin_bottom = 24
+script = ExtResource("1")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1251699420]
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 2
+size_flags_horizontal = 4
+alignment = 1
+script = ExtResource("2_dgrai")
+search_depth = 2
+
+[node name="FullscreenControl" parent="VBoxContainer" unique_id=1477535498 instance=ExtResource("3_uded6")]
+unique_name_in_owner = true
+layout_mode = 2
+option_name = "Fullscreen"
+option_section = 3
+key = "Fullscreen"
+section = "VideoSettings"
+
+[node name="ResolutionControl" parent="VBoxContainer" unique_id=646319285 instance=ExtResource("4_gwtfq")]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Select a screen size"
+option_values = [Vector2i(640, 360), Vector2i(960, 540), Vector2i(1024, 576), Vector2i(1280, 720), Vector2i(1600, 900), Vector2i(1920, 1080), Vector2i(2048, 1152), Vector2i(2560, 1440), Vector2i(3200, 1800), Vector2i(3840, 2160)]
+option_titles = Array[String](["640 x 360", "960 x 540", "1024 x 576", "1280 x 720", "1600 x 900", "1920 x 1080", "2048 x 1152", "2560 x 1440", "3200 x 1800", "3840 x 2160"])
+option_name = "Resolution"
+option_section = 3
+key = "ScreenResolution"
+section = "VideoSettings"
+property_type = 6
+
+[node name="VSyncControl" parent="VBoxContainer" unique_id=2127470729 instance=ExtResource("5_881de")]
+unique_name_in_owner = true
+layout_mode = 2
+lock_titles = true
+option_values = [0, 1, 2, 3]
+option_titles = Array[String](["Disabled", "Enabled", "Adaptive", "Mailbox"])
+option_name = "V-Sync"
+option_section = 3
+key = "V-Sync"
+section = "VideoSettings"
+property_type = 2
+default_value = 0
+
+[connection signal="setting_changed" from="VBoxContainer/FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
+[connection signal="setting_changed" from="VBoxContainer/ResolutionControl" to="." method="_on_resolution_control_setting_changed"]
+[connection signal="setting_changed" from="VBoxContainer/VSyncControl" to="." method="_on_v_sync_control_setting_changed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/music_players/background_music_player.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/music_players/background_music_player.tscn
new file mode 100644
index 0000000..5fa72db
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/music_players/background_music_player.tscn
@@ -0,0 +1,6 @@
+[gd_scene format=3 uid="uid://bkcsjsk2ciff"]
+
+[node name="BackgroundMusicPlayer" type="AudioStreamPlayer" unique_id=1446051342]
+process_mode = 3
+autoplay = true
+bus = &"Music"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.gd
new file mode 100644
index 0000000..d9e27e2
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.gd
@@ -0,0 +1,120 @@
+extends Control
+## Scene for displaying opening logos, placards, or other images before a game.
+
+## Defines the path to the next scene.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var next_scene_path : String
+## The list of images to show in the opening sequence.
+@export var images : Array[Texture2D]
+@export_group("Animation")
+## The time to fade-in the next image.
+@export var fade_in_time : float = 0.2
+## The time to fade-out the previous image.
+@export var fade_out_time : float = 0.2
+## The time to keep an image visible after fade-in and before fade-out.
+@export var visible_time : float = 1.6
+@export_group("Transition")
+## The delay before starting the first fade-in animation once ready.
+@export var start_delay : float = 0.5
+## The delay after ending the last fade-in animation before loading the next scene.
+@export var end_delay : float = 0.5
+## If true, show a loading screen if the next scene is not yet ready.
+@export var show_loading_screen : bool = false
+
+var tween : Tween
+var next_image_index : int = 0
+
+func get_next_scene_path() -> String:
+ if next_scene_path.is_empty():
+ return AppConfig.main_menu_scene_path
+ return next_scene_path
+
+func _on_scene_loaded() -> void:
+ SceneLoader.change_scene_to_resource()
+
+func _load_next_scene() -> void:
+ var status = SceneLoader.get_status()
+ if status == ResourceLoader.THREAD_LOAD_LOADED:
+ _on_scene_loaded()
+ elif show_loading_screen:
+ SceneLoader.change_scene_to_loading_screen()
+ elif not SceneLoader.scene_loaded.is_connected(_on_scene_loaded):
+ SceneLoader.scene_loaded.connect(_on_scene_loaded, CONNECT_ONE_SHOT)
+
+func _add_textures_to_container(textures : Array[Texture2D]) -> void:
+ for texture in textures:
+ var texture_rect : TextureRect = TextureRect.new()
+ texture_rect.texture = texture
+ texture_rect.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
+ texture_rect.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
+ texture_rect.modulate.a = 0.0
+ %ImagesContainer.call_deferred("add_child", texture_rect)
+
+func _event_skips_image(event : InputEvent) -> bool:
+ return event.is_action_released(&"ui_accept") or event.is_action_released(&"ui_select")
+
+func _event_skips_intro(event : InputEvent) -> bool:
+ return event.is_action_released(&"ui_cancel")
+
+func _event_is_mouse_button_released(event : InputEvent) -> bool:
+ return event is InputEventMouseButton and not event.is_pressed()
+
+func _unhandled_input(event : InputEvent) -> void:
+ if _event_skips_intro(event):
+ _load_next_scene()
+ elif _event_skips_image(event):
+ _show_next_image(false)
+
+func _gui_input(event : InputEvent) -> void:
+ if _event_is_mouse_button_released(event):
+ _show_next_image(false)
+
+func _transition_out() -> void:
+ await get_tree().create_timer(end_delay).timeout
+ _load_next_scene()
+
+func _transition_in() -> void:
+ await get_tree().create_timer(start_delay).timeout
+ if next_image_index == 0:
+ _show_next_image()
+
+func _wait_and_fade_out(texture_rect : TextureRect) -> void:
+ var _compare_next_index = next_image_index
+ await get_tree().create_timer(visible_time, false).timeout
+ if _compare_next_index != next_image_index : return
+ tween = create_tween()
+ tween.tween_property(texture_rect, "modulate:a", 0.0, fade_out_time)
+ await tween.finished
+ _show_next_image.call_deferred()
+
+func _hide_previous_image() -> void:
+ if tween and tween.is_running():
+ tween.stop()
+ if %ImagesContainer.get_child_count() == 0:
+ return
+ var current_image = %ImagesContainer.get_child(next_image_index - 1)
+ if current_image:
+ current_image.modulate.a = 0.0
+
+func _show_next_image(animated : bool = true) -> void:
+ _hide_previous_image()
+ if next_image_index >= %ImagesContainer.get_child_count():
+ if animated:
+ _transition_out()
+ else:
+ _load_next_scene()
+ return
+ var texture_rect = %ImagesContainer.get_child(next_image_index)
+ if animated:
+ tween = create_tween()
+ tween.tween_property(texture_rect, "modulate:a", 1.0, fade_in_time)
+ await tween.finished
+ else:
+ texture_rect.modulate.a = 1.0
+ next_image_index += 1
+ _wait_and_fade_out(texture_rect)
+
+func _ready() -> void:
+ SceneLoader.load_scene(get_next_scene_path(), true)
+ _add_textures_to_container(images)
+ _transition_in()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.gd.uid
new file mode 100644
index 0000000..4b25152
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.gd.uid
@@ -0,0 +1 @@
+uid://dtco0s8byckx6
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.tscn
new file mode 100644
index 0000000..cbe7122
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/opening/opening.tscn
@@ -0,0 +1,26 @@
+[gd_scene format=3 uid="uid://sikc02ddepyt"]
+
+[ext_resource type="Script" uid="uid://dtco0s8byckx6" path="res://addons/maaacks_game_template/base/nodes/opening/opening.gd" id="1_fcjph"]
+
+[node name="Opening" type="Control" unique_id=331014594]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_fcjph")
+
+[node name="BackgroundMusicPlayer" type="AudioStreamPlayer" parent="." unique_id=1317385298]
+process_mode = 3
+autoplay = true
+bus = &"Music"
+
+[node name="ImagesContainer" type="MarginContainer" parent="." unique_id=1471559661]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state.gd
new file mode 100644
index 0000000..9acf3b1
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state.gd
@@ -0,0 +1,50 @@
+class_name GlobalState
+extends Node
+
+const SAVE_STATE_PATH = "user://global_state.tres"
+const NO_VERSION_NAME = "0.0.0"
+
+static var current : GlobalStateData
+static var current_version : String
+
+static func _log_opened() -> void:
+ if current is GlobalStateData:
+ current.last_unix_time_opened = int(Time.get_unix_time_from_system())
+
+static func _log_version() -> void:
+ if current is GlobalStateData:
+ current_version = ProjectSettings.get_setting("application/config/version", NO_VERSION_NAME)
+ if current_version.is_empty():
+ current_version = NO_VERSION_NAME
+ if not current.first_version_opened:
+ current.first_version_opened = current_version
+ current.last_version_opened = current_version
+
+static func _load_current_state() -> void:
+ if FileAccess.file_exists(SAVE_STATE_PATH):
+ current = ResourceLoader.load(SAVE_STATE_PATH)
+ if not current:
+ current = GlobalStateData.new()
+
+static func open() -> void:
+ _load_current_state()
+ _log_opened()
+ _log_version()
+ save()
+
+static func save() -> void:
+ if current is GlobalStateData:
+ ResourceSaver.save(current, SAVE_STATE_PATH)
+
+static func has_state(state_key : String) -> bool:
+ if current is not GlobalStateData: return false
+ return current.has_state(state_key)
+
+static func get_or_create_state(state_key : String, state_type_path : String) -> Resource:
+ if current is not GlobalStateData: return
+ return current.get_or_create_state(state_key, state_type_path)
+
+static func reset() -> void:
+ if current is not GlobalStateData: return
+ current.states.clear()
+ save()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state.gd.uid
new file mode 100644
index 0000000..6c27059
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state.gd.uid
@@ -0,0 +1 @@
+uid://34ojrqt1klav
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state_data.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state_data.gd
new file mode 100644
index 0000000..d95f783
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state_data.gd
@@ -0,0 +1,24 @@
+class_name GlobalStateData
+extends Resource
+
+@export var first_version_opened : String
+@export var last_version_opened : String
+@export var last_unix_time_opened : int
+@export var states : Dictionary
+
+func get_or_create_state(key_name : String, state_type_path : String) -> Resource:
+ var new_state : Resource
+ var new_state_script = load(state_type_path)
+ if new_state_script is GDScript:
+ new_state = new_state_script.new()
+ if key_name in states:
+ var saved_state : Resource = states[key_name]
+ var saved_script = saved_state.get_script()
+ var new_script = new_state.get_script()
+ if saved_script and new_script and saved_script == new_script:
+ return saved_state
+ states[key_name] = new_state
+ return new_state
+
+func has_state(key_name : String) -> bool:
+ return key_name in states
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state_data.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state_data.gd.uid
new file mode 100644
index 0000000..533759a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/state/global_state_data.gd.uid
@@ -0,0 +1 @@
+uid://bb3tb71vb6p8w
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd
new file mode 100644
index 0000000..e6d41ef
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd
@@ -0,0 +1,68 @@
+extends Control
+## Node that captures UI focus when switching menus.
+##
+## This script assists with capturing UI focus when
+## opening, closing, or switching between menus.
+## When attached to a node, it will check if it was changed to visible
+## and if it should grab focus. If both are true, it will capture focus
+## on the first eligible node in its scene tree.
+
+## Hierarchical depth to search in the scene tree for a focusable control node.
+@export var search_depth : int = 1
+## If true, always capture focus when made visible.
+@export var enabled : bool = false
+## If true, capture focus if nothing currently is in focus.
+@export var null_focus_enabled : bool = true
+## If true, capture focus if there is a joypad detected.
+@export var joypad_enabled : bool = true
+## If true, capture focus if the mouse is hidden.
+@export var mouse_hidden_enabled : bool = true
+
+## Locks focus
+@export var lock : bool = false :
+ set(value):
+ var value_changed : bool = lock != value
+ lock = value
+ if value_changed and not lock:
+ update_focus()
+
+func _focus_first_search(control_node : Control, levels : int = 1) -> bool:
+ if control_node == null or !control_node.is_visible_in_tree():
+ return false
+ if control_node.focus_mode == FOCUS_ALL:
+ control_node.grab_focus()
+ if control_node is ItemList:
+ control_node.select(0)
+ return true
+ if levels < 1:
+ return false
+ var children = control_node.get_children()
+ for child in children:
+ if _focus_first_search(child, levels - 1):
+ return true
+ return false
+
+func focus_first() -> void:
+ _focus_first_search(self, search_depth)
+
+func update_focus() -> void:
+ if lock : return
+ if _is_visible_and_should_capture():
+ focus_first()
+
+func _should_capture_focus() -> bool:
+ return enabled or \
+ (get_viewport().gui_get_focus_owner() == null and null_focus_enabled) or \
+ (Input.get_connected_joypads().size() > 0 and joypad_enabled) or \
+ (Input.mouse_mode not in [Input.MOUSE_MODE_VISIBLE, Input.MOUSE_MODE_CONFINED] and mouse_hidden_enabled)
+
+func _is_visible_and_should_capture() -> bool:
+ return is_visible_in_tree() and _should_capture_focus()
+
+func _on_visibility_changed() -> void:
+ call_deferred("update_focus")
+
+func _ready() -> void:
+ if is_inside_tree():
+ update_focus()
+ connect("visibility_changed", _on_visibility_changed)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd.uid
new file mode 100644
index 0000000..b0eccab
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd.uid
@@ -0,0 +1 @@
+uid://1nf36h0gms3q
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/file_lister.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/file_lister.gd
new file mode 100644
index 0000000..8c1c9e4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/file_lister.gd
@@ -0,0 +1,55 @@
+@tool
+extends Node
+class_name FileLister
+## Helper class for listing all the scenes in a directory.
+
+## List of paths to scene files.
+@export var _refresh_files_action : bool = false :
+ set(value):
+ if value and Engine.is_editor_hint():
+ _refresh_files()
+# For Godot 4.4
+# @export_tool_button("Refresh Files") var _refresh_files_action = _refresh_files
+## Filled in the editor by selecting a directory.
+@export var files : Array[String]
+## Fills files with those discovered in directories, and matching constraints.
+@export_dir var directories : Array[String] :
+ set(value):
+ directories = value
+ _refresh_files()
+@export_group("Constraints")
+## Include any results that match the string.
+@export var search : String
+## Exclude any results that match the string.
+@export var filter : String
+@export_subgroup("Advanced Search")
+## Include any results that begin with the string.
+@export var begins_with : String
+## Include any results that end with the string.
+@export var ends_with : String
+## Exclude any results that begin with the string.
+@export var not_begins_with : String
+## Exclude any results that end with the string.
+@export var not_ends_with : String
+
+
+func _refresh_files():
+ if not is_inside_tree(): return
+ files.clear()
+ for directory in directories:
+ var dir_access = DirAccess.open(directory)
+ if dir_access:
+ for file in dir_access.get_files():
+ if (not search.is_empty()) and (not file.contains(search)):
+ continue
+ if (not filter.is_empty()) and (file.contains(filter)):
+ continue
+ if (not begins_with.is_empty()) and (not file.begins_with(begins_with)):
+ continue
+ if (not ends_with.is_empty()) and (not file.ends_with(ends_with)):
+ continue
+ if (not not_begins_with.is_empty()) and (file.begins_with(not_begins_with)):
+ continue
+ if (not not_ends_with.is_empty()) and (file.ends_with(not_ends_with)):
+ continue
+ files.append(directory + "/" + file)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/file_lister.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/file_lister.gd.uid
new file mode 100644
index 0000000..ae15737
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/file_lister.gd.uid
@@ -0,0 +1 @@
+uid://bij7wsh8d44gv
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/input_helper.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/input_helper.gd
new file mode 100644
index 0000000..affab91
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/input_helper.gd
@@ -0,0 +1,175 @@
+class_name InputEventHelper
+extends Node
+## Helper class for organizing constants related to [InputEvent].
+
+const DEVICE_KEYBOARD = "Keyboard"
+const DEVICE_MOUSE = "Mouse"
+const DEVICE_XBOX_CONTROLLER = "Xbox"
+const DEVICE_SWITCH_CONTROLLER = "Switch"
+const DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER = "Switch Left Joycon"
+const DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER = "Switch Right Joycon"
+const DEVICE_SWITCH_JOYCON_COMBINED_CONTROLLER = "Switch Combined Joycons"
+const DEVICE_PLAYSTATION_CONTROLLER = "Playstation"
+const DEVICE_STEAMDECK_CONTROLLER = "Steamdeck"
+const DEVICE_GENERIC = "Generic"
+
+const JOYSTICK_LEFT_NAME = "Left Stick"
+const JOYSTICK_RIGHT_NAME = "Right Stick"
+const D_PAD_NAME = "Dpad"
+
+const MOUSE_BUTTONS : Array = ["None", "Left", "Right", "Middle", "Scroll Up", "Scroll Down", "Wheel Left", "Wheel Right"]
+
+const JOYPAD_BUTTON_NAME_MAP : Dictionary = {
+ DEVICE_GENERIC : ["Trigger A", "Trigger B", "Trigger C", "", "", "", "", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right"],
+ DEVICE_XBOX_CONTROLLER : ["A", "B", "X", "Y", "View", "Home", "Menu", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right", "Share"],
+ DEVICE_SWITCH_CONTROLLER : ["B", "A", "Y", "X", "Minus", "", "Plus", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right", "Capture"],
+ DEVICE_PLAYSTATION_CONTROLLER : ["Cross", "Circle", "Square", "Triangle", "Select", "PS", "Options", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right", "Microphone"],
+ DEVICE_STEAMDECK_CONTROLLER : ["A", "B", "X", "Y", "View", "", "Options", "Left Stick Press", "Right Stick Press", "Left Shoulder", "Right Shoulder", "Up", "Down", "Left", "Right"]
+} # Dictionary[String, Array]
+
+const SDL_DEVICE_NAMES: Dictionary = {
+ DEVICE_XBOX_CONTROLLER: ["XInput", "XBox"],
+ DEVICE_PLAYSTATION_CONTROLLER: ["Sony", "PS5", "PS4", "Nacon"],
+ DEVICE_STEAMDECK_CONTROLLER: ["Steam"],
+ DEVICE_SWITCH_CONTROLLER: ["Switch"],
+ DEVICE_SWITCH_JOYCON_LEFT_CONTROLLER: ["Joy-Con (L)", "Left Joy-Con"],
+ DEVICE_SWITCH_JOYCON_RIGHT_CONTROLLER: ["Joy-Con (R)", "Right Joy-Con"],
+ DEVICE_SWITCH_JOYCON_COMBINED_CONTROLLER: ["Joy-Con (L/R)", "Combined Joy-Cons"],
+}
+
+const JOY_BUTTON_NAMES : Dictionary = {
+ JOY_BUTTON_A: "Button A",
+ JOY_BUTTON_B: "Button B",
+ JOY_BUTTON_X: "Button X",
+ JOY_BUTTON_Y: "Button Y",
+ JOY_BUTTON_LEFT_SHOULDER: "Left Shoulder",
+ JOY_BUTTON_RIGHT_SHOULDER: "Right Shoulder",
+ JOY_BUTTON_LEFT_STICK: "Left Stick",
+ JOY_BUTTON_RIGHT_STICK: "Right Stick",
+ JOY_BUTTON_START : "Button Start",
+ JOY_BUTTON_GUIDE : "Button Guide",
+ JOY_BUTTON_BACK : "Button Back",
+ JOY_BUTTON_DPAD_UP : D_PAD_NAME + " Up",
+ JOY_BUTTON_DPAD_DOWN : D_PAD_NAME + " Down",
+ JOY_BUTTON_DPAD_LEFT : D_PAD_NAME + " Left",
+ JOY_BUTTON_DPAD_RIGHT : D_PAD_NAME + " Right",
+ JOY_BUTTON_MISC1 : "Misc",
+}
+
+const JOYPAD_DPAD_NAMES : Dictionary = {
+ JOY_BUTTON_DPAD_UP : D_PAD_NAME + " Up",
+ JOY_BUTTON_DPAD_DOWN : D_PAD_NAME + " Down",
+ JOY_BUTTON_DPAD_LEFT : D_PAD_NAME + " Left",
+ JOY_BUTTON_DPAD_RIGHT : D_PAD_NAME + " Right",
+}
+
+const JOY_AXIS_NAMES : Dictionary = {
+ JOY_AXIS_TRIGGER_LEFT: "Left Trigger",
+ JOY_AXIS_TRIGGER_RIGHT: "Right Trigger",
+}
+
+const BUILT_IN_ACTION_NAME_MAP : Dictionary = {
+ "ui_accept" : "Accept",
+ "ui_select" : "Select",
+ "ui_cancel" : "Cancel",
+ "ui_focus_next" : "Focus Next",
+ "ui_focus_prev" : "Focus Prev",
+ "ui_left" : "Left (UI)",
+ "ui_right" : "Right (UI)",
+ "ui_up" : "Up (UI)",
+ "ui_down" : "Down (UI)",
+ "ui_page_up" : "Page Up",
+ "ui_page_down" : "Page Down",
+ "ui_home" : "Home",
+ "ui_end" : "End",
+ "ui_cut" : "Cut",
+ "ui_copy" : "Copy",
+ "ui_paste" : "Paste",
+ "ui_undo" : "Undo",
+ "ui_redo" : "Redo",
+}
+
+static func has_joypad() -> bool:
+ return Input.get_connected_joypads().size() > 0
+
+static func is_joypad_event(event: InputEvent) -> bool:
+ return event is InputEventJoypadButton or event is InputEventJoypadMotion
+
+static func is_mouse_event(event: InputEvent) -> bool:
+ return event is InputEventMouseButton or event is InputEventMouseMotion
+
+static func get_device_name_by_id(device_id : int) -> String:
+ if device_id >= 0:
+ var device_name = Input.get_joy_name(device_id)
+ for device_key in SDL_DEVICE_NAMES:
+ for keyword in SDL_DEVICE_NAMES[device_key]:
+ if device_name.containsn(keyword):
+ return device_key
+ return DEVICE_GENERIC
+
+static func get_device_name(event: InputEvent) -> String:
+ if event is InputEventJoypadButton or event is InputEventJoypadMotion:
+ if event.device == -1:
+ return DEVICE_GENERIC
+ var device_id = event.device
+ return get_device_name_by_id(device_id)
+ return DEVICE_GENERIC
+
+static func _display_server_supports_keycode_from_physical():
+ return OS.has_feature("windows") or OS.has_feature("macos") or OS.has_feature("linux")
+
+static func get_text(event : InputEvent) -> String:
+ if event == null:
+ return ""
+ if event is InputEventJoypadButton:
+ if event.button_index in JOY_BUTTON_NAMES:
+ return JOY_BUTTON_NAMES[event.button_index]
+ elif event is InputEventJoypadMotion:
+ var full_string := ""
+ var direction_string := ""
+ var is_right_or_down : bool = event.axis_value > 0.0
+ if event.axis in JOY_AXIS_NAMES:
+ return JOY_AXIS_NAMES[event.axis]
+ match(event.axis):
+ JOY_AXIS_LEFT_X:
+ full_string = JOYSTICK_LEFT_NAME
+ direction_string = "Right" if is_right_or_down else "Left"
+ JOY_AXIS_LEFT_Y:
+ full_string = JOYSTICK_LEFT_NAME
+ direction_string = "Down" if is_right_or_down else "Up"
+ JOY_AXIS_RIGHT_X:
+ full_string = JOYSTICK_RIGHT_NAME
+ direction_string = "Right" if is_right_or_down else "Left"
+ JOY_AXIS_RIGHT_Y:
+ full_string = JOYSTICK_RIGHT_NAME
+ direction_string = "Down" if is_right_or_down else "Up"
+ full_string += " " + direction_string
+ return full_string
+ elif event is InputEventKey:
+ var keycode : Key = event.get_physical_keycode()
+ if keycode:
+ keycode = event.get_physical_keycode_with_modifiers()
+ else:
+ keycode = event.get_keycode_with_modifiers()
+ if _display_server_supports_keycode_from_physical():
+ keycode = DisplayServer.keyboard_get_keycode_from_physical(keycode)
+ return OS.get_keycode_string(keycode)
+ return event.as_text()
+
+static func get_device_specific_text(event : InputEvent, device_name : String = "") -> String:
+ if device_name.is_empty():
+ device_name = get_device_name(event)
+ if event is InputEventJoypadButton:
+ var joypad_button : String = ""
+ if event.button_index in JOYPAD_DPAD_NAMES:
+ joypad_button = JOYPAD_DPAD_NAMES[event.button_index]
+ elif event.button_index < JOYPAD_BUTTON_NAME_MAP[device_name].size():
+ joypad_button = JOYPAD_BUTTON_NAME_MAP[device_name][event.button_index]
+ return "%s %s" % [device_name, joypad_button]
+ if event is InputEventJoypadMotion:
+ return "%s %s" % [device_name, get_text(event)]
+ if event is InputEventMouseButton:
+ if event.button_index < MOUSE_BUTTONS.size():
+ var mouse_button : String = MOUSE_BUTTONS[event.button_index]
+ return "%s %s" % [DEVICE_MOUSE, mouse_button]
+ return get_text(event).capitalize()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/input_helper.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/input_helper.gd.uid
new file mode 100644
index 0000000..7dbb6ea
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/input_helper.gd.uid
@@ -0,0 +1 @@
+uid://6xujceamar4h
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd
new file mode 100644
index 0000000..7369503
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd
@@ -0,0 +1,31 @@
+extends Node
+
+## Node for opening a pause menu when detecting a 'ui_cancel' event.
+
+@export var pause_menu_packed : PackedScene
+@export var focused_viewport : Viewport
+
+var pause_menu : Node
+
+func pause() -> void:
+ if pause_menu.visible: return
+ if not focused_viewport:
+ focused_viewport = get_viewport()
+ var _initial_focus_control = focused_viewport.gui_get_focus_owner()
+ pause_menu.show()
+ if pause_menu is CanvasLayer:
+ await pause_menu.visibility_changed
+ else:
+ await pause_menu.hidden
+ if is_inside_tree() and _initial_focus_control:
+ _initial_focus_control.grab_focus()
+
+# If pause menu should take precedence, override _input() instead.
+func _unhandled_input(event : InputEvent) -> void:
+ if event.is_action_pressed("ui_cancel"):
+ pause()
+
+func _ready() -> void:
+ pause_menu = pause_menu_packed.instantiate()
+ pause_menu.hide()
+ get_tree().current_scene.call_deferred("add_child", pause_menu)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd.uid
new file mode 100644
index 0000000..b1138e6
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd.uid
@@ -0,0 +1 @@
+uid://cyh0d64pfygbl
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd
new file mode 100644
index 0000000..9d1b1fa
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd
@@ -0,0 +1,20 @@
+@tool
+class_name ConfirmationOverlaidWindow
+extends OverlaidWindow
+
+signal confirmed
+
+@onready var confirm_button : Button = %ConfirmButton
+
+@export var confirm_button_text : String = "Confirm" :
+ set(value):
+ confirm_button_text = value
+ if update_content and is_inside_tree():
+ confirm_button.text = confirm_button_text
+
+func confirm():
+ confirmed.emit()
+ close()
+
+func _on_confirm_button_pressed():
+ confirm()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd.uid
new file mode 100644
index 0000000..5d647aa
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd.uid
@@ -0,0 +1 @@
+uid://bgthh72eu0du
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn
new file mode 100644
index 0000000..775d6dd
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn
@@ -0,0 +1,23 @@
+[gd_scene format=3 uid="uid://cwt4p3bufkke5"]
+
+[ext_resource type="PackedScene" uid="uid://6gdbfi0172ji" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_vfkm2"]
+[ext_resource type="Script" uid="uid://bgthh72eu0du" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.gd" id="2_sw7p1"]
+
+[node name="ConfirmationOverlaidWindow" unique_id=1482267070 instance=ExtResource("1_vfkm2")]
+script = ExtResource("2_sw7p1")
+confirm_button_text = "Confirm"
+update_content = true
+close_button_text = "Cancel"
+
+[node name="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" parent_id_path=PackedInt32Array(1413292752) index="0" unique_id=1371114575]
+vertical = false
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0" unique_id=314526102]
+text = "Cancel"
+
+[node name="ConfirmButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1" unique_id=1052970550]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Confirm"
+
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ConfirmButton" to="." method="_on_confirm_button_pressed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd
new file mode 100644
index 0000000..9912a86
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd
@@ -0,0 +1,77 @@
+@tool
+class_name OverlaidWindow
+extends WindowContainer
+
+@export var pauses_game : bool = false :
+ set(value):
+ pauses_game = value
+ if pauses_game:
+ process_mode = PROCESS_MODE_ALWAYS
+ else:
+ process_mode = PROCESS_MODE_INHERIT
+@export var makes_mouse_visible : bool = true
+@export var exclusive : bool = true
+@export var exclusive_background_color : Color
+
+var _initial_pause_state : bool = false
+var _initial_mouse_mode : Input.MouseMode
+var _initial_focus_control
+var _initial_node_focus_modes : Dictionary
+var _scene_tree : SceneTree
+var _exclusive_control_node : ColorRect
+
+func _set_focus_none(node : Node) -> void:
+ for child in node.get_children():
+ if child == self: continue
+ if child is Control:
+ _initial_node_focus_modes[child] = child.focus_mode
+ child.focus_mode = Control.FOCUS_NONE
+ _set_focus_none(child)
+
+func _set_focus_initial() -> void:
+ for node in _initial_node_focus_modes:
+ if is_instance_valid(node) and node is Control:
+ node.focus_mode = _initial_node_focus_modes[node]
+ _initial_node_focus_modes.clear()
+
+func close() -> void:
+ if not visible: return
+ _scene_tree.paused = _initial_pause_state
+ Input.set_mouse_mode(_initial_mouse_mode)
+ _set_focus_initial()
+ if is_instance_valid(_initial_focus_control) and _initial_focus_control.is_inside_tree():
+ _initial_focus_control.grab_focus()
+ if _exclusive_control_node:
+ _exclusive_control_node.queue_free()
+ super.close()
+
+func _overlaid_window_setup():
+ if _scene_tree:
+ _initial_pause_state = _scene_tree.paused
+ _initial_mouse_mode = Input.get_mouse_mode()
+ _initial_focus_control = get_viewport().gui_get_focus_owner()
+ if _initial_focus_control:
+ _initial_focus_control.release_focus()
+ if Engine.is_editor_hint(): return
+ _scene_tree.paused = pauses_game or _initial_pause_state
+ if makes_mouse_visible:
+ Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
+ if exclusive:
+ _set_focus_none(get_tree().current_scene)
+ _exclusive_control_node = ColorRect.new()
+ _exclusive_control_node.name = self.name + "ExclusiveControl"
+ _exclusive_control_node.color = exclusive_background_color
+ _exclusive_control_node.set_anchors_preset(PRESET_FULL_RECT)
+ add_sibling.call_deferred(_exclusive_control_node)
+ await _exclusive_control_node.draw
+ get_parent().move_child(_exclusive_control_node, get_index())
+
+func _on_visibility_changed() -> void:
+ if is_visible_in_tree():
+ _overlaid_window_setup()
+
+func _enter_tree() -> void:
+ _scene_tree = get_tree()
+ if not visibility_changed.is_connected(_on_visibility_changed):
+ visibility_changed.connect(_on_visibility_changed)
+ _on_visibility_changed()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd.uid
new file mode 100644
index 0000000..4a7fdb9
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd.uid
@@ -0,0 +1 @@
+uid://xfugmpspqbcc
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn
new file mode 100644
index 0000000..c1f7881
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn
@@ -0,0 +1,12 @@
+[gd_scene format=3 uid="uid://6gdbfi0172ji"]
+
+[ext_resource type="Script" uid="uid://xfugmpspqbcc" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.gd" id="1_euyj1"]
+[ext_resource type="PackedScene" uid="uid://b2s0kvrx8r2kq" path="res://addons/maaacks_game_template/base/nodes/windows/window_container.tscn" id="2_pmk27"]
+
+[node name="OverlaidWindow" unique_id=343706911 instance=ExtResource("2_pmk27")]
+process_mode = 0
+script = ExtResource("1_euyj1")
+pauses_game = false
+makes_mouse_visible = true
+exclusive = true
+exclusive_background_color = Color(0, 0, 0, 0.5)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd
new file mode 100644
index 0000000..c35d5fb
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd
@@ -0,0 +1,19 @@
+@tool
+class_name OverlaidWindowContainer
+extends OverlaidWindow
+
+var instance : Node
+@onready var scene_container : Container = %SceneContainer
+
+@export var packed_scene : PackedScene :
+ set(value):
+ packed_scene = value
+ if is_inside_tree():
+ for child in scene_container.get_children():
+ child.queue_free()
+ if packed_scene:
+ instance = packed_scene.instantiate()
+ scene_container.add_child(instance)
+
+func _ready() -> void:
+ packed_scene = packed_scene
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd.uid
new file mode 100644
index 0000000..c049d95
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd.uid
@@ -0,0 +1 @@
+uid://c6pmyo50c1tqy
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.tscn
new file mode 100644
index 0000000..79e5ba5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.tscn
@@ -0,0 +1,13 @@
+[gd_scene format=3 uid="uid://crndfbb22ri4s"]
+
+[ext_resource type="PackedScene" uid="uid://6gdbfi0172ji" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_07348"]
+[ext_resource type="Script" uid="uid://c6pmyo50c1tqy" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd" id="2_p673y"]
+
+[node name="OverlaidWindowSceneContainer" unique_id=232665413 instance=ExtResource("1_07348")]
+script = ExtResource("2_p673y")
+packed_scene = null
+
+[node name="SceneContainer" type="MarginContainer" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="1" unique_id=1464754606]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.gd b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.gd
new file mode 100644
index 0000000..98ec82f
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.gd
@@ -0,0 +1,80 @@
+@tool
+class_name WindowContainer
+extends PanelContainer
+
+signal closed
+signal opened
+
+@export var ui_cancel_closes : bool = true
+
+@export_group("Content")
+@export var update_content : bool = false
+@export_multiline var text : String :
+ set(value):
+ text = value
+ if update_content and is_inside_tree():
+ description_label.text = text
+
+@export var close_button_text : String = "Close" :
+ set(value):
+ close_button_text = value
+ if update_content and is_inside_tree():
+ close_button.text = close_button_text
+
+@export_subgroup("Title")
+@export var title : String = "Menu" :
+ set(value):
+ title = value
+ if update_content and is_inside_tree():
+ title_label.text = title
+
+@export_range(0, 1000, 1) var title_font_size : int = 16 :
+ set(value):
+ title_font_size = value
+ if update_content and is_inside_tree():
+ title_label.set("theme_override_font_sizes/font_size", title_font_size)
+
+@export var title_visible : bool = true :
+ set(value):
+ title_visible = value
+ if update_content and is_inside_tree():
+ title_margin.visible = title_visible
+
+@onready var content_container : Container = %ContentContainer
+@onready var title_label : Label = %TitleLabel
+@onready var title_margin : MarginContainer = %TitleMargin
+@onready var description_label : RichTextLabel = %DescriptionLabel
+@onready var close_button : Button = %CloseButton
+@onready var menu_buttons : BoxContainer = %MenuButtons
+
+func _ready() -> void:
+ update_content = update_content
+ text = text
+ close_button_text = close_button_text
+ title = title
+ title_font_size = title_font_size
+ title_visible = title_visible
+
+func close() -> void:
+ if not visible: return
+ hide()
+ closed.emit()
+
+func _handle_cancel_input() -> void:
+ close()
+
+func _unhandled_input(event : InputEvent) -> void:
+ if visible and event.is_action_released("ui_cancel") and ui_cancel_closes:
+ _handle_cancel_input()
+ get_viewport().set_input_as_handled()
+
+func _on_close_button_pressed() -> void:
+ close()
+
+func show() -> void:
+ super.show()
+ opened.emit()
+
+func _exit_tree():
+ if Engine.is_editor_hint(): return
+ close()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.gd.uid
new file mode 100644
index 0000000..49e5380
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.gd.uid
@@ -0,0 +1 @@
+uid://b3onujul5qho1
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.tscn b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.tscn
new file mode 100644
index 0000000..1673f6b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/nodes/windows/window_container.tscn
@@ -0,0 +1,90 @@
+[gd_scene format=3 uid="uid://b2s0kvrx8r2kq"]
+
+[ext_resource type="Script" uid="uid://b3onujul5qho1" path="res://addons/maaacks_game_template/base/nodes/windows/window_container.gd" id="1_te2s1"]
+[ext_resource type="Script" uid="uid://1nf36h0gms3q" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_xihbi"]
+
+[node name="WindowContainer" type="PanelContainer" unique_id=2023947717]
+process_mode = 3
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -80.0
+offset_top = -50.0
+offset_right = 80.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+script = ExtResource("1_te2s1")
+
+[node name="ContentContainer" type="MarginContainer" parent="." unique_id=1755486830]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/margin_left = 16
+theme_override_constants/margin_top = 16
+theme_override_constants/margin_right = 16
+theme_override_constants/margin_bottom = 16
+
+[node name="BoxContainer" type="BoxContainer" parent="ContentContainer" unique_id=394030069]
+layout_mode = 2
+vertical = true
+
+[node name="TitleMargin" type="MarginContainer" parent="ContentContainer/BoxContainer" unique_id=1262022916]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/margin_left = -14
+theme_override_constants/margin_top = -14
+theme_override_constants/margin_right = -14
+theme_override_constants/margin_bottom = 8
+
+[node name="BoxContainer" type="BoxContainer" parent="ContentContainer/BoxContainer/TitleMargin" unique_id=1788474031]
+layout_mode = 2
+theme_override_constants/separation = 0
+vertical = true
+
+[node name="TitleLabel" type="Label" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" unique_id=1049966061]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_font_sizes/font_size = 16
+text = "Menu"
+horizontal_alignment = 1
+
+[node name="HSeparator" type="HSeparator" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" unique_id=33373596]
+layout_mode = 2
+
+[node name="BodyMargin" type="MarginContainer" parent="ContentContainer/BoxContainer" unique_id=590613964]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="DescriptionLabel" type="RichTextLabel" parent="ContentContainer/BoxContainer/BodyMargin" unique_id=617407155]
+unique_name_in_owner = true
+layout_mode = 2
+bbcode_enabled = true
+fit_content = true
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="MenuButtonsMargin" type="MarginContainer" parent="ContentContainer/BoxContainer" unique_id=1413292752]
+layout_mode = 2
+theme_override_constants/margin_top = 8
+
+[node name="MenuButtons" type="BoxContainer" parent="ContentContainer/BoxContainer/MenuButtonsMargin" unique_id=1371114575]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(128, 0)
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 16
+alignment = 1
+vertical = true
+script = ExtResource("2_xihbi")
+
+[node name="CloseButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" unique_id=314526102]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Close"
+
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/CloseButton" to="." method="_on_close_button_pressed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/translations/menus_translations.csv b/evolve-die-repeat/addons/maaacks_game_template/base/translations/menus_translations.csv
new file mode 100644
index 0000000..287a5b1
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/translations/menus_translations.csv
@@ -0,0 +1,104 @@
+keys,en,fr
+
+___ MAIN MENU,,
+
+Title,Title,Titre
+Subtitle,Subtitle,Sous-titre
+New Game,New Game,
+Continue,Continue,
+Play,Play,Jouer
+Level Select,Level Select,
+Options,Options,Options
+Credits,Credits,Crédits
+Exit,Exit,Quitter
+"Are you sure you want to start a new game?
+
+All progress in the current game will be lost.","Are you sure you want to start a new game?
+
+All progress in the current game will be lost.",
+
+___ LOADING SCREEN,,
+Loading...,Loading...,Chargement...
+Still Loading...,Still Loading...,
+Loading Complete!,Loading Complete!,
+Any Moment Now...,Any Moment Now...,
+
+___ DIALOGS IN GAME,,
+
+Win,Win,
+Lose,Lose,
+Tutorial,Tutorial,
+Change Level Color: ,Change Level Color: ,
+Close,Close,
+
+Level complete!,Level complete!,
+You lost...,You lost...,Vous avez perdu...
+You won!,You won!,Vous avez gagné !
+Thanks for playing!,Thanks for playing!,Merci d'avoir joué !
+
+Exit Game,Exit Game,Quitter le jeu
+Main Menu,Main Menu,Menu principal
+Restart,Restart,Recommencer
+Continue,Continue,Continuer
+Menu,Menu,Menu
+
+Please Confirm...,Please Confirm...,Veuillez confirmer...
+Go back to main menu?,Go back to main menu?,Retourner au menu principal ?
+Quit the game?,Quit the game?,Quitter le jeu ?
+Cancel,Cancel,Annuler
+OK,OK,OK
+
+__ TUTORIALS,,
+
+"Click the Win button to progress.
+Click the Lose button to try again.","Click the Win button to progress.
+Click the Lose button to try again.",
+"Progress is saved.
+Pressing Continue from the main menu will load the last level played.","Progress is saved.
+Pressing Continue from the main menu will load the last level played.",
+"The color picker at the bottom-right updates the level state. This change persists until the game is reset.
+
+The label at the bottom-center displays the current input action detected, if any are setup for the project.","The color picker at the bottom-right updates the level state. This change persists until the game is reset.
+
+The label at the bottom-center displays the current input action detected, if any are setup for the project.",
+
+___ OPTIONS MENU,,
+
+Controls,Controls,Contrôles
+Mouse Sensitivity :,Mouse Sensitivity :,Sensibilité souris :
+Actions & Inputs,Actions & Inputs,Actions et contrôles
+Add,Add,Ajouter
+Remove,Remove,Enlever
+Assign Key for {action},Assign Key for {action},Choisir le contrôle pour {action}
+Listening for input...,Listening for input...,Appuyez sur un bouton...
+Press again to confirm...,Press again to confirm...,Appuyez encore pour confirmer...
+Focus here to assign inputs.,Focus here to assign inputs.,Mettez le focus ici pour choisir le contrôle.
+Already Assigned,Already Assigned,Déjà utilisé
+{key} already assigned to {action}.,{key} already assigned to {action}.,{key} est déjà utilisé pour {action}.
+Remove Key for {action},Remove Key for {action},Supprimer le contrôle pour {action}
+Are you sure you want to remove {key} from {action}?,Are you sure you want to remove {key} from {action}?,Êtes-vous sûr de vouloir supprimer {key} pour {action} ?
+Reset,Reset,Réinitialiser
+
+Audio,Audio,Audio
+Master :,Master :,Principal :
+Music :,Music :,Musique :
+SFX :,SFX :,Effets :
+Mute :,Mute :,Silencieux :
+
+Video,Video,Vidéo
+Fullscreen :,Fullscreen :,Plein écran :
+Resolution :,Resolution :,Résolution :
+Anti-Aliasing :,Anti-Aliasing :,Anticrénelage :
+Disabled (Fastest),Disabled (Fastest),Désactivé (Plus rapide)
+8x (Slowest),8x (Slowest),8x (Plus lent)
+Camera Shake :,Camera Shake :,Secousse Caméra :
+Normal,Normal,Normale
+Reduced,Reduced,Réduite
+Minimal,Minimal,Minimum
+None,None,Aucune
+
+Game,Game,Jeu
+Reset Game :,Reset Game :,Réinitialiser le jeu :
+Do you want to reset your game data?,Do you want to reset your game data?,Voulez-vous réinitialiser votre partie ?
+
+Back,Back,Retour
diff --git a/evolve-die-repeat/addons/maaacks_game_template/base/translations/menus_translations.csv.import b/evolve-die-repeat/addons/maaacks_game_template/base/translations/menus_translations.csv.import
new file mode 100644
index 0000000..f38bb71
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/base/translations/menus_translations.csv.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="csv_translation"
+type="Translation"
+uid="uid://i6ihop1vp2ei"
+
+[deps]
+
+files=["res://addons/maaacks_game_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_game_template/base/translations/menus_translations.fr.translation"]
+
+source_file="res://addons/maaacks_game_template/base/translations/menus_translations.csv"
+dest_files=["res://addons/maaacks_game_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_game_template/base/translations/menus_translations.fr.translation"]
+
+[params]
+
+compress=true
+delimiter=0
+unescape_keys=false
+unescape_translations=true
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/AddingCustomOptions.md b/evolve-die-repeat/addons/maaacks_game_template/docs/AddingCustomOptions.md
new file mode 100644
index 0000000..adf7575
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/AddingCustomOptions.md
@@ -0,0 +1,49 @@
+# Adding Custom Options
+
+> [!WARNING]
+> This page is being deprecated in favor of [Options Menus Setup](/addons/maaacks_game_template/docs/OptionsMenusSetup.md).
+
+This page covers adding new buttons, sliders, or editable text fields to the options menus that automatically persist between sessions.
+
+## To the Menu
+Custom options can be added to a menu without any code.
+
+1. Add an `option_control.tscn` node as a child to a container in a scene.
+ 1. `slider_option_control.tscn` or `toggle_option_control.tscn` can be used if those types match requirements. In that case, skip step 6.
+ 2. `list_option_control.tscn` and `vector_2_list_option_control.tscn` are also available, but more complicated. See the `ScreenResolution` example.
+3. Select the `OptionControl` node just added, to edit it in the inspector.
+4. Add an `Option Name`. This prefills the `Key` string.
+5. Select an `Option Section`. This prefills the `Section` string.
+6. Add any kind of `Button`, `Slider`, `LineEdit`, or `TextEdit` to the `OptionControl` node.
+7. Save the scene.
+
+## To the Game
+For options to have any effect outside of the menus, they will need to be referenced by their `key` and `section` from the `PlayerConfig` class.
+```
+PlayerConfig.get_config(key, section)
+```
+
+For example, here is how to get the player's desired input sensitivity for controlling a player camera.
+```
+var mouse_sensitivity : float = PlayerConfig.get_config(AppSettings.INPUT_SECTION, "MouseSensitivity", 1.0)
+var joypad_sensitivity : float = PlayerConfig.get_config(AppSettings.INPUT_SECTION, "JoypadSensitivity", 1.0)
+```
+
+## Validation
+ Validate the values being stored in your local `player_config.cfg` file.
+1. Refer to [Accessing Persistent User Data User](https://docs.godotengine.org/en/stable/tutorials/io/data_paths.html#accessing-persistent-user-data-user) to find Godot user data on your machine.
+2. Find the directory that matches your project's name.
+3. Open `player_config.cfg` (should be in the top directory of the project).
+4. Find the section by the section name in brackets, and the key name followed by an equals.
+
+For example, here is how the player's desired input sensitivity could appear in the config file.
+
+```
+[InputSettings]
+
+MouseSensitivity=1.05
+JoypadSensitivity=0.95
+```
+
+> [!NOTE]
+> Some settings may not appear until they have been customized.
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/AddingUISFX.md b/evolve-die-repeat/addons/maaacks_game_template/docs/AddingUISFX.md
new file mode 100644
index 0000000..639fde1
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/AddingUISFX.md
@@ -0,0 +1,29 @@
+# Adding UI Sound Effects
+
+This page covers adding sound effects to common UI elements like buttons and sliders.
+
+1. Verify the `Music` and `SFX` audio busses.
+
+ 1. Open the Audio bus editor.
+ 2. Confirm that `Music` and `SFX` audio busses are available.
+ 1. If the last bus is `New Bus`, try restarting the editor and checking again.
+ 3. If the audio bus doesn't exist, add it and save the project.
+
+
+1. Add UI sound effects:
+ 1. By scene.
+
+
+ 1. Open `main_menu_with_animations.tscn` and `pause_menu.tscn`.
+ 2. In the Scene Tree, select the `UISoundController` node.
+ 3. In the Inspector, add audio streams to the various UI node events.
+ 4. Save the scenes.
+
+
+ 2. Project-wide.
+
+
+ 1. Open `project_ui_sound_controller.tscn`.
+ 2. In the Scene Tree, select the `UISoundController` node.
+ 3. In the Inspector, add audio streams to the various UI node events.
+ 4. Save the scene.
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/AutomaticUpdating.md b/evolve-die-repeat/addons/maaacks_game_template/docs/AutomaticUpdating.md
new file mode 100644
index 0000000..d6f879e
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/AutomaticUpdating.md
@@ -0,0 +1,30 @@
+# Automatic Updating
+
+This plugin automatically checks GitHub for new releases. When a new release is available, the option to update will appear in the `Project > Tools` menu.
+
+## Starting an Update
+
+> [!IMPORTANT]
+> Save the state of the project, and close all open scenes and scripts.
+
+Select the option from `Project > Tools > Update Maaack's Game Template to v...`.
+
+A window will pop-up, confirming the choice to update to the latest release. Select `OK`.
+
+Another window will show progress through downloading, saving, and extracting.
+
+This effectively deletes the current `addons/maaacks_game_template/` folder and replaces it with a new one. Nothing outside of `addons/` should be affected.
+
+After, a window should appear confirming a successful update.
+
+## Disabling Automatic Checking
+
+You can disable the automatic update checks by going into the Project Settings, and enabling the `maaacks_game_template/disable_update_check` setting. You can then close the window.
+
+## Issues
+
+If the option to update does not appear, try restarting the editor, or re-enabling the plugin.
+
+Updating adds the examples folder into the `addons/maaacks_game_template/` folder, if it had been deleted previously.
+
+Files already copied from the examples folder will not be affected by an update. However, a mismatch of versions may cause issues, too. If there are no major customizations to the copied files, it is recommended to delete them and recopy from `Project > Tools > Run Maaack's Game Template Setup...`.
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/BasicSetup.md b/evolve-die-repeat/addons/maaacks_game_template/docs/BasicSetup.md
new file mode 100644
index 0000000..53f0bcf
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/BasicSetup.md
@@ -0,0 +1,68 @@
+# Basic Setup
+
+These instructions cover the basics for setting up the template.
+
+## Setup Wizard
+
+The _Setup Wizard_ shows the user's progress through the setup process.
+
+### Open
+
+Open the _Setup Wizard_ by navigating to `Project > Tools > Run Maaack's Game Template Setup...`.
+
+
+
+### Check & Complete
+
+A typical full installation will have the following steps completed:
+- Using Latest Version
+- Copy Example Files
+- Delete Example Files
+- Update Autoload Paths
+- Set Main Scene
+
+
+
+Depending on how the template was installed, or if any issues occurred, some of these may need to be run from the wizard to be completed.
+
+The remaining steps are optional customizations.
+
+## Scene Paths
+
+The flow of scenes in the template is (by default):
+```
+Opening -> Main Menu -> Game Scene -> Ending Scene
+```
+
+To change the _Main Menu_, _Game Scene_, or _Ending Scene_:
+
+1. Open `app_config.tscn`.
+2. Select the `AppConfig` node.
+3. Update `Main Menu Scene Path` to the desired path (`main_menu_with_animations.tscn` by default).
+4. Update `Game Scene Path` to the path of the project's game scene (`game_ui.tscn` by default).
+5. Update the optional `Ending Scene Path` to the desired scene (`end_credits.tscn` by default).
+6. Save the scene.
+
+To change the _Opening_:
+
+1. Navigate to `Project > Project Settings…`
+2. In the _Project Settings_ window, go to the `General` tab.
+3. In the settings list, navigate to `Application > Run`.
+4. Update `Main Scene` to the desired path.
+
+## Next Steps
+
+### Recommended
+1. [Main Menu Setup](/addons/maaacks_game_template/docs/MainMenuSetup.md)
+2. [Options Menu Setup](/addons/maaacks_game_template/docs/OptionsMenuSetup.md)
+3. [Game Scene Setup](/addons/maaacks_game_template/docs/GameSceneSetup.md)
+4. [Updating Credits](/addons/maaacks_game_template/docs/UpdatingCredits.md)
+5. [Blending Music](/addons/maaacks_game_template/docs/BlendingMusic.md)
+6. [Adding UI Sound Effects](/addons/maaacks_game_template/docs/AddingUISFX.md)
+
+### Extra
+1. [Adding Icons to the Input Options](/addons/maaacks_game_template/docs/InputIconMapping.md)
+2. [Supporting Joypad Inputs](/addons/maaacks_game_template/docs/JoypadInputs.md)
+3. [Loading scenes asynchronously](/addons/maaacks_game_template/docs/LoadingScenes.md)
+4. [Utilizing Game Saving](/addons/maaacks_game_template/docs/GameSaving.md)
+5. [Uploading to itch.io](/addons/maaacks_game_template/docs/UploadingToItchIo.md)
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/BlendingMusic.md b/evolve-die-repeat/addons/maaacks_game_template/docs/BlendingMusic.md
new file mode 100644
index 0000000..e1ef67f
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/BlendingMusic.md
@@ -0,0 +1,42 @@
+# Blending Music Between Scenes
+
+This page covers the `ProjectMusicController`, which is used to blend music in between scenes, that would otherwise abruptly stop the music when they get unloaded.
+
+## Setup
+
+1. Verify the `Music` audio bus.
+
+ 1. Open the Audio bus editor.
+ 2. Confirm that `Music` audio bus is available.
+ 1. If the last bus is `New Bus`, try restarting the editor and checking again.
+ 3. If the audio bus doesn't exist, add it and save the project.
+
+2. Verify the `ProjectMusicController` autoload.
+
+ 1. Open the Project Settings.
+ 2. Open the `Globals` tab.
+ 3. Confirm that the `ProjectMusicController` is listed as an autoload.
+
+3. Setup music blending between scenes.
+
+ 1. Open up the `project_music_controller.tscn` scene.
+ 2. Inspect the root node of the scene tree (`ProjectMusicController`).
+ 3. Confirm that the `audio_bus` is set to `Music`.
+ 4. Expand the `Blending` variable group.
+ 5. Set a `fade_out_duration` or `fade_in_duration`, in seconds.
+
+4. Add background music to your scenes.
+
+ 1. Import the music asset into the project.
+ 2. Add a `BackgroundMusicPlayer`.
+ 3. Assign the music asset to the `stream` property.
+ 4. Make sure that the `bus` property is set to `Music` and `autoplay` is `true`.
+ 5. Save the scene.
+
+## Internal Details
+
+When a background music player is about to exit the scene tree, it gets reparented to the autoload node. This allows it to continue playing until the next scene is ready and a new background music player is detected. If the audio stream players share the same stream, then the controller will seek the next player to the same position on the stream, before removing the previous one. If a different stream is detected and a fade out duration is set, then the previous player will blend into the next one, by having its volume lowered to zero over the fade out duration.
+
+The autload adds the "BlendMusic" audio bus is added at runtime. If a fade in duration is set, then the temporary bus is used to combine the increasing volume of the player with any other animations local to the scene.
+
+The autoload will work with any `AudioStreamPlayer` with `bus` set to `Music` and `autoplay` set to `true`. These are detected up as they enter the scene tree. To dynamically add an `AudioStreamPlayer` to the background music, call `ProjectMusicController.play_stream(background_music_player)`.
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/BuildAndPublish.md b/evolve-die-repeat/addons/maaacks_game_template/docs/BuildAndPublish.md
new file mode 100644
index 0000000..807cbe5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/BuildAndPublish.md
@@ -0,0 +1,304 @@
+## How to Build and Publish my game using Github CICD?
+
+**GitHub** is a platform that hosts your project’s source code online, making it easy to collaborate, track changes, and share your game with players, testers, or colleagues.
+
+**CI/CD** (Continuous Integration and Continuous Deployment) refers to automating the process of building, testing, and publishing your game whenever you make updates. The idea is to speed up your game's release process so you can push updates frequently, to fix bugs quicker or add more game content.
+
+Using GitHub Actions, you can set up **workflows that automatically compile your Godot project** and **upload it to platforms** like itch.io whenever you tag a new release. This saves time, reduces manual errors, and helps keep your build and release process smooth and repeatable.
+
+**How does it work?** Once everything is set up, publish a new version of your game by creating a new **Github Release**. This will trigger the Github Action, that will build your game in the cloud and publish it to itch.io with a nice version tag.
+
+> Note: You can set up all of this and still keep your game as a _Draft_ on itch.io. This is great for playtesting!
+
+## Prerequisites
+
+Before following this guide, make sure you have the following in place:
+
+- **GitHub Account & Repository:** Your Godot project should be pushed to a GitHub repository. If you haven’t yet, create one and upload your project files.
+- **Itch.io Account:** You’ll need an itch.io account to publish your game. Create one at itch.io if you don’t have it yet.
+
+## Setup your game build
+
+### 1. Setup exports for your game in Godot
+
+First, open your game in Godot. Go to Project > Export... and make sure to add the following exports:
+
+- `Web`
+- `Linux`
+- `macOS`
+- `Windows Desktop`
+
+
+
+Then, run the export for one platform manually **at least once.** This will create a `export_presets.cfg` file at the root of your project.
+
+The `build-and-publish.yml` will **trigger** the build configs in `export_presets.cfg` **by name**. So make sure that your exports names are the same as in the list above. You can change the names or add more build configs with small edits to `build-and-publish.yml`.
+
+#### Additional Git setup
+Some version of Godot will add `export_presets.cfg` to `.gitignore` automatically. You'll want to remove that, so that git checks in your export configuration file with the rest of your code.
+
+#### Additional MacOS setup
+
+##### MacOS Bundle name
+
+For MacOS, you have a field called "Bundle name" with default value `com.game.maaack-template`. Change this to be your game name.
+
+##### MacOS Notarization
+
+By default, your built game on MacOS will be flagged as dangerous. Players will need to allow its execution by going into _System Settings > Privacy & Security_, allow the app execution, and restart the game.
+
+To avoid this, you need to notarize your game, i.e. tell Apple who you are and what is your binary.
+
+For that, you'll need first to create an Apple developer account (99USD/year). Then, you'll need to adapt the Export configuration of MacOS [using this guide](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_macos.html#if-you-have-an-apple-developer-id-certificate-and-exporting-from-linux-or-windows) to add **rcodesign** notarization and your Apple tokens.
+
+### 2. Create `GODOT_VERSION` and `EXPORT_NAME` variables
+
+Go to your Github repository Settings > Secrets and Variables > Actions. Then, select the **Variables** tab.
+
+Create two **Repository Variables**: `GODOT_VERSION` and `EXPORT_NAME`.
+
+> [!NOTE]
+> Repository variables will be available for this Github repository only, [but you can do more complex stuff if required](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-variables).
+> Using variables is great, because repo admins can still see these values in Github and edit them.
+
+Change the `EXPORT_NAME` to fit the name of your game. This will be the name of the file your players download.
+
+By default, the workflow file is made for Godot 4.6, but you can set `GODOT_VERSION` to the version of Godot for your project. This will be used for loading container images and export templates. This workflow file uses [godot-ci](https://github.com/abarichello/godot-ci?tab=readme-ov-file) to build your game, so make sure the Godot version you're referring to is [available on Docker.](https://hub.docker.com/r/barichello/godot-ci/tags)
+
+### 3. Copy the `build-and-publish.yml` file
+
+Copy the file `addons/maaacks_game_template/extras/scripts/build-and-publish.yml` into the `.github/workflows` folder at the root of your github repository.
+
+Then, push the file to github on your main branch. The workflow file will be detected by github as a Github Action.
+
+### 4. (Optional) Edit the export platforms
+
+The workflow file is made in two parts:
+
+1. First, it builds your game
+2. Then, it pushes the builds to itch.io
+
+By default, the workflow file tries to build configs named `Web`, `Linux`, `macOS` and `Windows Desktop`, and will fail if one of the configs is not available.
+
+#### Deleting a platform
+
+If you **don't want** to export to one platform, **delete** the build jobs and the publish steps in the `build-and-publish.yml`.
+
+For example, if you don't want to export for macOS, delete this part which builds the artifact:
+
+```yml
+ export-mac:
+ name: macOS Export
+ runs-on: ubuntu-24.04
+ container:
+ image: barichello/godot-ci:${{ vars.GODOT_VERSION }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Setup
+ run: |
+ mkdir -v -p ~/.local/share/godot/export_templates/
+ mkdir -v -p ~/.config/
+ mv /root/.config/godot ~/.config/godot || true
+ mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable || true
+
+ - name: Mac Build
+ run: |
+ mkdir -v -p build/mac
+ EXPORT_DIR="$(readlink -f build)"
+ cd $PROJECT_PATH
+ godot --headless --verbose --export-release "macOS" "$EXPORT_DIR/mac/${EXPORT_NAME}-mac.zip"
+
+ - name: Upload to GitHub Release (if this run is a release)
+ if: ${{ github.event_name == 'release' }}
+ uses: svenstaro/upload-release-action@v2
+ with:
+ file: build/mac/${{ env.EXPORT_NAME }}-mac.zip
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: mac
+ path: build/mac
+
+```
+
+Remove the job ID (ie. `export-mac`) from the needs of the `publish-builds` job:
+```yml
+ publish-builds:
+ name: Publish Builds
+ needs: [export-web, export-windows, export-linux, export-mac]
+```
+
+And remove this part, which publishes it to itch.io:
+
+```yml
+ - name: Upload to Itch.io - macOS
+ run: |
+ ./butler push builds/mac ${{ env.ITCH_USERNAME }}/${{ env.ITCH_GAME }}:mac --userversion "${{ steps.version.outputs.version }}"
+```
+
+#### Adding a platform
+If you want to export to a **new** platform, **copy paste** the build job and add a new step to itch.io publication.
+
+In the build job, change:
+ 1. the name of the **export config** that you created in Godot.
+ 2. the paths in which the build artifact is created.
+
+For example:
+
+```yml
+- name: NEW_PLATFORM Build
+ run: |
+ mkdir -v -p build/NEW_PLATFORM
+ EXPORT_DIR="$(readlink -f build)"
+ cd $PROJECT_PATH
+ godot --headless --verbose --export-release "BUILD_CONFIG_NAME" "$EXPORT_DIR/NEW_PLATFORM/$EXPORT_NAME.zip"
+```
+
+In the itch.io publication step, make sure to change the path and the tag.
+
+```yml
+- name: Upload to Itch.io - NEW_PLATFORM_TAG
+ run: |
+ ./butler push builds/NEW_PLATFORM ${{ env.ITCH_USERNAME }}/${{ env.ITCH_GAME }}:NEW_PLATFORM_TAG --userversion "${{ steps.version.outputs.version }}"
+
+```
+
+## Setup Itch.io publication
+
+### 1. Create a new project on itch.io
+
+1. Go to [itch.io](https://itch.io/), click on the top right and **Upload a New Project**.
+
+2. Fill in the game name and any information you want, but don't upload any file.
+
+3. If you plan to have a **Web build**, select the **Kind of project** to be **HTML** instead of **Downloadable**.
+
+4. Save the project as a **Draft** (you can change this to public later, once you tested that everything works).
+
+### 2. Create `ITCH_USERNAME` and `ITCH_GAME` variables
+
+To find your itch.io username and the name of your game, look at the url of your project: `https://your-username.itch.io/your-game`. The username is the first part of the URL, and the game name is in the last part.
+
+Then, go to your Github repository Settings > Secrets and Variables > Actions. Then, select the **Variables** tab.
+
+Create two **Repository Variables**: `ITCH_USERNAME` and `ITCH_GAME`.
+
+You should have something like this (with your real username and your real game name instead, and any other repository variables):
+
+
+
+### 3. Create a `BUTLER_API_KEY` Github secret
+
+1. Install [butler.](https://itch.io/docs/butler/installing.html) This is the official CLI tool for itch.io
+
+2. Unzip and make sure the bin is executable
+
+ ```bash
+ chmod +x butler
+ ```
+
+3. Run
+
+ ```bash
+ butler login
+ ```
+
+ This should open your browser. Login and allow butler to access your account.
+
+ 
+
+ In the terminal, the login flow will conclude with something like this:
+
+ ```
+ Authenticated successfully! Saving key in /Users/username/Library/Application Support/itch/butler_creds...
+ ```
+
+ **Note:** if you're already logged in into butler but forgot the path to credentials, use `butler login -h` to show the default location of `butler_creds`.
+
+4. Get your butler API key by reading the content of this file. Beware of spaces in the filepath! This will show you a 40 characters string which is your butler API key.
+
+ ```bash
+ cat "/Users/username/Library/Application Support/itch/butler_creds"
+ ```
+
+ > **Warning:** your butler API key is sensitive and secret. **Do not** share it with anyone, **do not** commit it to your repository, and **do not** add it directly to the workflow file.
+
+5. [Create a new Github secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets) for your Github repository. Go into Settings > Secrets and Variables > Actions and select the **Secrets** tab.
+
+ Create a new **Repository Secret** with name `BUTLER_API_KEY` (this secret will be available only in this repository). Inside, paste the 40 characters string of the previous step.
+
+ 
+
+ > It's important to use Github Secrets here because their values are encrypted and hidden from everyone, even repo admins. This ensures privacy and security.
+
+## How to publish the game?
+
+Congrats, you're ready to create a new Github Release and automatically publish updates!
+
+When you’re ready to publish a new version of your game, create a **GitHub release** tied to a version tag on the `main` branch. Using releases helps track updates, distribute builds, and communicate changes to players or testers.
+
+A new release will trigger the `build-and-publish.yml` workflow, which will **build your game** in the cloud and **publish it** to itch.io (if everything is setup).
+
+1. Ensure all desired changes are merged into the `main` branch. This is the version that'll get built and published.
+2. On Github, go to **Release**, then **draft a new release** ([here is a step by step guide](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release)). Create a new tag on you `main` branch using [semantic versioning](https://semver.org/). As a best practice, also prefix it with `v` in Github.
+ - `x.0.0` — Major Release. Large updates or milestones (e.g., new game systems, overhauled visuals, major gameplay changes). Example: `v1.0.0` for the full launch.
+ - `x.y.0` — Minor Update. New content or features that expand gameplay but remain backward-compatible. Example: `v1.1.0` for new levels or mechanics.
+ - `x.y.z` — Patch / Hotfix. Small updates, bug fixes, performance improvements, or balancing tweaks. Example: `v1.1.3` for fixing a crash or visual glitch.
+3. Publish the release. This will trigger the CICD in Github Actions. Monitor its execution and check for errors in the **Actions** tab on Github.
+
+## Troubleshooting: Enable the HTML / Playable version of your game on itch
+
+It may happen that you don't see the HTML version of your game as playable, but just as a file.
+
+
+
+What you need to do is edit your itch project to change the **Kind of project** to be **HTML** instead of **Downloadable**.
+
+
+
+Then, edit the `html5` channel and toggle **This file will be played in the browser**.
+
+
+
+Going back to your project page, you should now see the HTML version of your game playable in the browser on itch.io page.
+
+
+
+## Next steps
+
+Once your CI/CD pipeline is running smoothly, take it a step further:
+
+- **Pre-Release Testing:** Add a test stage in your workflow to validate your project before publishing (for example, by running Godot unit tests or verifying builds).
+- **Multi-Platform Deployment:** Add Android and iOS build, or remove the builds you don't use.
+- **Other Distribution Platforms:** Adapt the CI/CD pipeline to push releases to other platforms like Steam, Google Play, App Store, Epic Games Store...
+- **Add notarization for MacOS:** That's a best practice for a smoother experience.
+
+## Sources
+
+- **GitHub Documentation**
+
+ - [Creating a Release on GitHub](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release)
+ - [Using GitHub Secrets in Workflows](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets)
+
+- **Semantic Versioning**
+
+ - [Semantic Versioning 2.0.0](https://semver.org/)
+
+- **Godot CI / Docker**
+
+ - [abarichello/godot-ci (GitHub)](https://github.com/abarichello/godot-ci?tab=readme-ov-file)
+ - [barichello/godot-ci Docker Hub Tags](https://hub.docker.com/r/barichello/godot-ci/tags)
+
+- **Godot Engine Documentation**
+
+ - [Exporting for macOS (with Apple Developer ID)](https://docs.godotengine.org/en/latest/tutorials/export/exporting_for_macos.html#if-you-have-an-apple-developer-id-certificate-and-exporting-from-linux-or-windows)
+
+- **itch.io Resources**
+
+ - [itch.io Main Website](https://itch.io/)
+ - [Butler CLI Installation Guide](https://itch.io/docs/butler/installing.html)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/Exhibiting.md b/evolve-die-repeat/addons/maaacks_game_template/docs/Exhibiting.md
new file mode 100644
index 0000000..fc1fc40
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/Exhibiting.md
@@ -0,0 +1,72 @@
+# Exhibiting Games
+
+This page covers general best practices for exhibiting your game at conventions like PAX, Gamescom, etc. You generally don't want people to just close your game and do potentially malicious stuff on the computer, especially if it is your personal computer.
+
+## General Best Practices
+
+- Create a separate non-admin account on your computer and use that to run the game.
+- Disable the Alt-Tab shortcut in your operating system.
+ - Windows: Install PowerToys and use its Keyboard Manager to disable it.
+
+## In Godot
+
+- Create a custom [feature tag](https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html#custom-features) for an exhibition build.
+- Create a autoload script that blocks quitting, sets the window mode to exclusive fullscreen, and sets the mouse mode to confined.
+- Create custom actions for quitting the game.
+ - These can be a combination of letters that are unlikely to be pressed by a player during the game.
+ - Scripting an input timer can avoid an accidental press by a player from quitting the game.
+ - These should be kept hidden from the player.
+- Create a exhibition build by adding the corresponding feature tag to a build preset.
+
+### In the Game Template
+- Hide the custom quit action(s) by setting `show_all_actions` to `false` for the `InputActionsList` and `InputActionsTree` nodes.
+Update: Input Options Menu
+- Disable quitting with the `ui_cancel` action using the feature tag.
+Update:
+ - Main Menu
+ - End Credits
+- Hide all quit buttons using the feature tag.
+Update:
+ - Main Menu
+ - Pause Menu
+ - Overlaid Menu
+ - Game Won Menu
+ - Level Lost Menu
+- Disable the fullscreen toggle.
+Update: Video Options Menu
+
+
+### Example
+
+Autoload Script:
+```swift
+extends Node
+
+var sum: float
+
+func _ready() -> void:
+ if not OS.has_feature("exhibition"):
+ queue_free()
+ return
+ get_tree().set_auto_accept_quit(false)
+ get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN
+ get_window().always_on_top = true
+ Input.mouse_mode = Input.MOUSE_MODE_CONFINED
+
+func _process(delta: float) -> void:
+ if (Input.is_action_pressed("exh_quit_one") and Input.is_action_pressed("exh_quit_two")):
+ sum += delta
+ if sum >= 3:
+ get_tree().quit()
+ else:
+ sum = 0
+```
+
+Disable a quit button by checking for the additional feature flag.
+Example from `end_credits.gd`:
+```swift
+if OS.has_feature("web") or OS.has_feature("exhibition"):
+ %ExitButton.hide()
+```
+
+An example implementation is available at [git.rostige-pipe.de](https://git.rostige-pipe.de/JaN0h4ck/fetziges-raetsel-spiel/commit/c65efc79e4460e88fecb8b8e81ce1edcfbbf9617#diff-32a77ee954ef131428f29b9ca647142bfaca06bf).
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/ExistingProject.md b/evolve-die-repeat/addons/maaacks_game_template/docs/ExistingProject.md
new file mode 100644
index 0000000..a9f4352
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/ExistingProject.md
@@ -0,0 +1,57 @@
+# Existing Project
+
+> [!WARNING]
+> This page is being deprecated in favor of [Basic Setup](/addons/maaacks_game_template/docs/BasicSetup.md).
+
+These instructions assume starting with just the contents of `addons/` and going through the installer to copy the examples content into your project. This will be the case when installing the *plugin* version in the Godot Asset Library.
+
+To revisit any part of the initial setup, find the `Setup Wizard` at `Project > Tools > Run Maaack's Game Template Setup...`. Example files can be re-copied from the `Setup Wizard`, assuming they have not been deleted.
+
+1. Update the project’s name in the main menu.
+
+
+ 1. Open `main_menu_with_animations.tscn`.
+ 2. Select the `TitleLabel` node.
+ 3. The `Text` should match the project's name (in the project's settings).
+ 1. If `Text` is customized, set `Auto Update` to false.
+ 4. Select the `SubtitleLabelNode` node and customize the `Text` as desired.
+ 5. Save the scene.
+
+
+2. Link the main menu to a custom game scene (skip if using the example game scene).
+
+
+ 1. Open `main_menu_with_animations.tscn`.
+ 2. Select the `MainMenu` node.
+ 3. Update `Game Scene Path` to the path of the project's game scene.
+ 4. Save the scene.
+
+
+3. Add / remove configurable settings to / from menus.
+
+
+ 1. Open `mini_options_menu.tscn` or `[audio|visual|input|game]_options_menu.tscn` scenes to edit their options.
+ 2. If an option is not desired, it can always be hidden, or removed entirely (sometimes with some additional work).
+ 3. If a new option is desired, refer to [Adding Custom Options.](/addons/maaacks_game_template/docs/AddingCustomOptions.md)
+
+
+4. Update the game credits / attribution.
+
+
+ 1. Update the example `ATTRIBUTION.md` with the project's credits.
+ 2. Open `credits_label.tscn`.
+ 3. Check the `CreditsLabel` has updated with the text.
+ 4. Optionally, disable `Auto Update` and customize the text.
+ 5. Save the scene (even if it shows no changes).
+
+
+5. Continue with:
+
+ 1. [Setting up the Main Menu.](/addons/maaacks_game_template/docs/MainMenuSetup.md)
+ 2. [Setting up a Game Scene.](/addons/maaacks_game_template/docs/GameSceneSetup.md)
+ 3. [Loading scenes asynchronously.](/addons/maaacks_game_template/docs/LoadingScenes.md)
+ 4. [Adding icons to the Input Options.](/addons/maaacks_game_template/docs/InputIconMapping.md)
+ 5. [Blending Music.](/addons/maaacks_game_template/docs/BlendingMusic.md)
+ 6. [Adding UI Sound Effects.](/addons/maaacks_game_template/docs/AddingUISFX.md)
+ 7. [Adding Custom Options.](/addons/maaacks_game_template/docs/AddingCustomOptions.md)
+ 8. [Utilizing Game Saving.](/addons/maaacks_game_template/docs/GameSaving.md)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/GameSaving.md b/evolve-die-repeat/addons/maaacks_game_template/docs/GameSaving.md
new file mode 100644
index 0000000..7c31737
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/GameSaving.md
@@ -0,0 +1,44 @@
+# Game Saving
+
+> [!IMPORTANT]
+> The save system doesn't follow the same conventions as other systems.
+> It is subject to change.
+
+> [!WARNING]
+> The save system relies on resource files, which are vulnerable to having malicious scripts injected into them.
+> Please discourage players from sharing their save files. Do not use this for cloud saving, either.
+> A safer save system is planned.
+
+
+The templates and plugin suite aim to keep most class definitions within the addon. These are not expected to be changed directly. Unlike the other classes, the `GameState` and `LevelState` are defined for the developer to edit to their needs.
+
+## Usage
+
+The `GlobalState` static class keeps the state saved to a resource. The developer is responsible for making sure `GlobalState.save()` gets called when they want the state saved to the disk.
+
+### Game State
+
+The `GameState` class represents the state of a single playthrough of the game. It currently stores the current level, the max level reached, and the state of each level currently visited.
+
+It is currently expected to be used as a singleton, too.
+
+
+### Level State
+
+The `LevelState` class represents the state of a single level in a playthrough of the game. It currently stores whether the tutorial has been read, and a color, if the player has set one in the example levels. It can be used to store the states of many other level specific features.
+
+From within the `_ready()` method of a level scene, call `GameState.get_level_state(scene_file_path)` to get the last saved `LevelState`, or a new one, and then set the state of the level from that. When a state of the level changes that is intended to be preserved, save it into the level state, and call `GlobalState.save()`.
+
+Examples are provided allowing the player to save the level background color, and keeping the tutorial message from popping up more than once per playthrough.
+
+## Internal Details
+
+Many features provided in the example rely on the `GameStateExample` or `LevelStateExample` to work properly.
+
+The main menu will check the current state to see if a game is in progress and show a `Continue` button. It can also optionally show a `Level Select` button if the player is at least on the 2nd level.
+
+The `LevelManager` in the game UI scene (using `level_and_state_manager.gd`) checks the current state for the level it should load at each stage.
+
+Lastly, the optional level select menu checks the state to determine which levels to display as options to the player.
+
+Data is saved as a resource file in the player's local user directory.
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/GameSceneSetup.md b/evolve-die-repeat/addons/maaacks_game_template/docs/GameSceneSetup.md
new file mode 100644
index 0000000..46e7eec
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/GameSceneSetup.md
@@ -0,0 +1,103 @@
+# Game Scene Setup
+
+When setting up a game scene, it is useful to refer to the `game_scene/game_ui.tscn` included in the examples.
+
+There are a few parts to setting up a basic game scene, as done in the `GameUI` example used in the template.
+
+## Pausing
+The `PauseMenuController` node can be added to the tree, or the `pause_menu_controller.gd` script may be attached to an empty `Node`. Selecting the node should then allow for setting the `pause_menu_packed` value in the inspector. Set it to the `pause_menu_layer.tscn` (or `pause_menu.tscn`) scene and save.
+
+This should be enough to capture when the `ui-cancel` input action is pressed in-game. On keyboards, this is commonly the `Esc` key.
+
+## Level Loading
+Some level loading scripts are provided with the examples. They load levels in order from a list, or dynamically by file paths.
+
+The `LevelManager` manages the progress through levels. It works with a level loader and can open menus when players win or lose. With a child `SceneLister`, it manages progression linearly. Otherwise, it will rely on the levels themselves providing the path to the next level. It can either be assigned a starting level path (for open world progression) or start from the first level in a scene lister (for linear level progression).
+
+The specific `level_and_state_manager.gd` in the scene inherits from `LevelManager` in order to sync progress with the player's `GameState`, as well.
+
+A `LevelLoader` loads levels, attaches them to a container, and manages a loading screen. It must be provided with a `level_container` in the scene. The example uses the `SubViewport`, but any leaf node (ie. node without children) in the scene should work. An optional `level_loading_screen` in the scene can be attached to show progress of loading levels.
+
+### Linear Level Progress
+With linear progression, the path in the `starting_level_path` setting can be removed. Levels can be added to the `SceneLister` by either selecting a directory to automatically find scenes, or populating the files array manually.
+
+If using the level select scene, then the `SceneLister` there will also need to be updated to match.
+
+By default, the manager will open the first level from the `SceneLister`. It'll then set the checkpoint to the next level in the list when the current level is won. When winning the last level, it'll load the window for the game being won.
+
+### Non-Linear Level Progress
+Alternatively, with open world progression, the reference in the `scene_lister` setting can be removed. Instead, the path to the next level is expected to be provided by the current level. The example levels demonstrate this with the `next_level_path` setting.
+
+By default, the manager will open `starting_level_path`. It'll then set the checkpoint to the next level sent in the `level_won` or `level_changed` signal from the current level. If no level path is provided, it'll load the window for the game being won.
+
+### Games without levels
+Level Loading is not required if the entire game takes place in one scene.
+
+In that case, the following nodes can be safely removed:
+* LevelLoader
+* LevelManager
+* LevelLoadingScreen
+
+The single level scene can then be added directly to the `SubViewport`, another container, or the root node.
+
+To manage the win and lose screens and transitioning to other scenes, add a `Node` and attach the `win_lose_manager.gd` script. Inspect the node to attach the win / lose screens and paths. The `game_won()` or `game_lost()` will then need to be called when gameplay conditions are met.
+
+## Background Music
+`BackgroundMusicPlayer`'s are `AudioStreamPlayer`'s with `autoplay` set to `true` and `audio_bus` set to "Music". These will automatically be recognized by the `ProjectMusicController` with the default settings, and allow for blending between tracks.
+
+A `BackgroundMusicPlayer` can be added to the main game scene, but if using levels, the level scenes are typically a better place for them, as that allows for tracks to vary by level.
+
+## SubViewports
+The game example has the levels loaded into a `SubViewport` node, contained within a `SubViewportContainer`. This has a couple of advantages.
+
+- Separates elements intended to appear inside the game world from those intended to appear on a layer above it.
+- Allows setting a fixed resolution for the game, like pixel art games.
+- Allows setting rendering settings, like anti-aliasing, on the `SubViewport`.
+- Supports easily adding visual effects with shaders on the `SubViewportContainer`.
+- Visual effects can be added to the game world without hurting the readability of the UI.
+
+It has some disadvantages, as well.
+
+- Locks the viewport resolution if any scaling is enabled, which is not ideal for 3D games.
+- Requires enabling Audio Listeners to hear audio from the game world.
+- Extra processing overhead for the viewport layer.
+
+If a subviewport does not work well for the game, use any empty `Node` as the game world or level container, instead.
+
+### Pixel Art Games
+If working with a pixel art game, often the goal is that the number of art pixels on-screen is to remain the same regardless of screen resolution. As in, the art scales with the monitor, rather than bigger monitors showing more of a scene. This is done by setting the viewport size in the project settings, and setting the stretch mode to either `canvas_mode` or `viewport`.
+
+If a higher resolution is desired for the menus and UI than the game, then the project viewport size should be set to a multiple of the desired game window size. Then set the stretch shrink in `SubViewportContainer` to the multiple of the resolution. For example, if the game is at `640x360`, then the project viewport size can be set to `1280x720`, and the stretch shrink set to `2` (`1280x720 / 2 = 640x360`). Finally, set the texture filter on the `SubViewportContainer` to `Nearest`.
+
+### Mouse Interaction
+If trying to detect `mouse_enter` and `mouse_exit` events on areas inside the game world, enable physics object picking on the `SubViewport`.
+
+## Read Inputs
+Generally, any game is going to require reading some inputs from the player. Where in the scene hierarchy the reading occurs is best answered with simplicity.
+
+If the game involves moving a player character, then the inputs for movements could be read by a `player_character.gd` script overriding the `_process(delta)` or `_input(event)` methods.
+
+If the game involves sending commands to multiple units, then those inputs probably should be read by a `game_ui.gd` script, that then propagates those calls further down the chain.
+
+## Win & Lose Screens
+The example includes win and lose screens. These are triggered by the `LevelManager` when a level is won or lost.
+
+```
+func _load_level_complete_screen_or_next_level():
+ if level_won_scene:
+ var instance = level_won_scene.instantiate()
+ get_tree().current_scene.add_child(instance)
+ ...
+ else:
+ _load_next_level()
+```
+Winning on the last level results in loading a win screen or ending for the game.
+
+```
+func _on_level_won():
+ if is_on_last_level():
+ _load_win_screen_or_ending()
+ else:
+ _load_level_won_screen_or_next_level()
+```
+The `LevelManager` will need to be linked to direct back to the main menu and optionally forward to an end credits.
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/GamesMade.md b/evolve-die-repeat/addons/maaacks_game_template/docs/GamesMade.md
new file mode 100644
index 0000000..0559962
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/GamesMade.md
@@ -0,0 +1,71 @@
+# Games
+This page features games using Maaack's Godot Game Template and/or plugins.
+
+If you have a game you'd like to share, join the [Discord server](https://discord.gg/AyZrJh5AMp ) and post a link to your game in #showcase.
+
+## Featured
+
+| HeartFix Express | Baking Godium | Rent Seek Kill |
+| :-------:| :-------: | :-------: |
+|  |  |  |
+| [Find on Steam](https://store.steampowered.com/app/3983290/HeartFix_Express_Demo/) | [Play on itch.io](https://maaack.itch.io/baking-godium) | [Play on itch.io](https://xandruher.itch.io/rent-seek-kill) |
+
+## All Shared
+### 2026
+https://store.steampowered.com/app/3983290/HeartFix_Express_Demo/
+https://store.steampowered.com/app/4029840/Zero_Warning_Burnt_Out/
+https://maaack.itch.io/scout-quest
+https://chippper.itch.io/hand-me-that
+
+### 2025
+https://sevadusk.itch.io/liferoot
+https://maaack.itch.io/baking-godium
+https://baconeggsrl.itch.io/umbra-city
+https://store.steampowered.com/app/3911550/Warp_Marked_Demo/
+https://maaack.itch.io/kobo-expansion
+https://keur-collectif.itch.io/heartfix-express-demo
+https://spacecheese.itch.io/cia
+https://acul4321.itch.io/bleep
+https://redspine.itch.io/gmtk-game-jam-2025
+https://zunarii.itch.io/loopinball
+https://dragonruler1000.itch.io/beep
+https://store.steampowered.com/app/3751730/Loan_Shark/
+https://parallaxrat.itch.io/no-mans-land
+https://baconeggsrl.itch.io/sprouts-journey
+https://maaack.itch.io/indys-expedition-2
+https://maaack.itch.io/absurd-herd
+https://maaack.itch.io/dungeon-fantasy-fashion-show
+https://plexsoup.itch.io/factoriohno
+https://maaack.itch.io/furnace-in-the-archive
+https://schinken.itch.io/low-ink
+
+### 2024
+https://store.steampowered.com/app/3291880/Spud_Customs/ (Source: https://github.com/Lost-Rabbit-Digital/SpudCustoms)
+https://glockenberg.itch.io/icefire-temple
+https://maaack.itch.io/backroom-labyrinths
+https://maaack.itch.io/haunted-circuits
+https://maaack.itch.io/talk-up-the-tower
+https://marinaaaa.itch.io/meowntaineer
+https://maaack.itch.io/a-darkness-like-gravity
+https://maaack.itch.io/lore-of-the-wild-gwj-70
+https://maaack.itch.io/infinite-horizon
+https://elidef.itch.io/forge-ur-boss
+https://maaack.itch.io/forgeomino
+https://xandruher.itch.io/rent-seek-kill
+https://maaack.itch.io/blind-escape-gwj-66-edition
+https://justaguyjustaguy.itch.io/nannybot-overload
+https://maaack.itch.io/the-last-host-boss-rush
+https://kyveri.itch.io/riverking
+
+### 2023
+https://xandruher.itch.io/spectral-war
+https://maaack.itch.io/the-cat-with-eight-gwj-63-edition
+https://maaack.itch.io/harvest-hill-gwj-62-edition
+https://shoddygames.itch.io/once-summoned
+https://maaack.itch.io/the-last-host
+https://maaack.itch.io/do-androids-dream-gwj-55-edition
+https://maaack.itch.io/character-builder-gwj-53-edition
+
+### 2022
+https://maaack.itch.io/rit-dot-wav
+https://maaack.itch.io/supercritical-a-post-apocalyptic-bonsai
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/HowPartsWork.md b/evolve-die-repeat/addons/maaacks_game_template/docs/HowPartsWork.md
new file mode 100644
index 0000000..021cf6b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/HowPartsWork.md
@@ -0,0 +1,16 @@
+# How Parts Work
+
+This page features snippets of extra documentation on key pieces of the plugin. It was previously included in the README.
+
+- `app_config.tscn` is an autoload scene. It calls `app_settings.gd` to load all the configuration settings from the config file (if it exists) through `player_config.gd`.
+- `scene_loader.tscn` is an autoload scene. It can load scenes in the background or with a loading screen (`loading_screen.tscn` by default).
+- `opening.tscn` is a simple scene for fading in/out a few images at the start of the game. It then loads the next scene (`main_menu.tscn`).
+- `main_menu.tscn` is where a player can start the game, change settings, watch credits, or quit. It can link to the path of a game scene to play, and the packed scene of an options menu to use.
+- `option_control.tscn` and its inherited scenes are used for most configurable options in the menus. They work with `player_config.gd` to keep settings persistent between runs.
+- `credits_label.tscn` reads from `ATTRIBUTION.md` to automatically generate the content for it's scrolling text label.
+- The `UISoundController` node automatically attaches sounds to buttons, tab bars, sliders, and line edits in the scene. `project_ui_sound_controller.tscn` is an autload used to apply UI sounds project-wide.
+- `project_music_controller.tscn` is an autoload that keeps music playing between scenes. It detects music stream players as they are added to the scene tree, reparents them to itself, and blends the tracks.
+- The `PauseMenuController` can be set to load `pause_menu_layer.tscn` (or `pause_menu.tscn`) when triggering `ui-cancel`.
+- `pause_menu_layer.tscn` is a type of `OverlaidMenu` with a parent `CanvasLayer` and the `pauses_game` flag set to true. It will store the previously focused UI element, and return focus to it when closed.
+- `capture_focus.gd` is attached to container nodes throughout the UI. It focuses onto UI elements when they are shown, allowing for easier navigation without a mouse.
+- `game_ui.tscn` is a demo game scene that displays recognized action inputs, and features the `PauseMenuController` node, the `LevelLoader` node to load levels into a container, and `LevelManager` to manage level progress and show menus in case of a win or loss.
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/InputIconMapping.md b/evolve-die-repeat/addons/maaacks_game_template/docs/InputIconMapping.md
new file mode 100644
index 0000000..b35b856
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/InputIconMapping.md
@@ -0,0 +1,165 @@
+# Input Icon Mapping
+
+The `InputIconMapper` in `input_options_menu.tscn` is a generalized tool meant to be broadly compatible with freely licensed icon asset packs. Instructions on how to use it with a few of these packs are provided, with links to download them from their creator's page.
+
+## Kenney Input Prompts
+
+### Automatic
+
+> [!IMPORTANT]
+> Save the state of the project, and close all open scenes and scripts.
+
+With the project open, select `Project > Tools > Run Maaack's Game Template Setup...`.
+
+In the `Setup Wizard` window next to "Add Input Prompt Icons", click `Run`.
+
+In the next window, select a style and then wait for the icons to download, extract, and setup.
+
+If the icons have already been installed before, you will be presented with the option to skip re-downloading.
+
+> [!WARNING]
+> This may crash the editor.
+> In that event, check if the process completed, and try running the setup again.
+
+### Manual
+
+Available from [kenney.nl](https://kenney.nl/assets/input-prompts) and [itch.io](https://kenney-assets.itch.io/input-prompts).
+
+This pack is organized by `Device/IconType`. The `IconTypes` for each device are just `Default`, `Vector`, or `Double`. These instructions will assume using `Default`. In the inspector of `InputIconMapper`, set the `directories` to include the subdirectories of the asset pack.
+* `.../kenney_input-prompts/Keyboard & Mouse/Default`
+* `.../kenney_input-prompts/Generic/Default`
+* `.../kenney_input-prompts/Xbox Series/Default`
+* `.../kenney_input-prompts/PlayStation Series/Default`
+* `.../kenney_input-prompts/Nintendo Switch/Default`
+* `.../kenney_input-prompts/Steam Deck/Default`
+
+Set `filtered_strings` to:
+* `keyboard`
+* `color`
+* `button`
+* `arrow`
+
+Set `replace_strings` with the key pairs:
+* `"Capslock": "Caps Lock"`
+* `"Generic Stick": "Generic Left Stick"`
+* `"Guide": "Home"`
+* `"Slash Back": "Back Slash"`
+* `"Slash Forward": "Slash"`
+* `"Stick L": "Left Stick"`
+* `"Stick R": "Right Stick"`
+* `"Trigger L 1": "Left Shoulder"`
+* `"Trigger L 2": "Left Trigger"`
+* `"Trigger R 1": "Right Shoulder"`
+* `"Trigger R 2": "Right Trigger"`
+
+#### Filled Icons
+
+Under the `FileLister` properties of the `InputIconMapper`, expand the `Constraints` and `Advanced Search` tabs. Set `ends_with=".png"` and `not_ends_with="outline.png"`.
+
+Press `Refresh Files`.
+
+If you want to use colored icons, in `prioritized_strings` add `color`. Otherwise set `filter="color"`.
+
+Press `Match Icons to Inputs`.
+
+Validate the results by inspecting the `matching_icons` dictionary.
+
+#### Outlined Icons
+
+Not all icons have outlined versions, so we will end up including the filled icons as fallback, and prioritizing outlined.
+
+Under the `FileLister` properties of the `InputIconMapper`, expand the `Constraints` and `Advanced Search` export groups. Set `ends_with=".png"`.
+
+Press `Refresh Files`.
+
+Add to `filtered_strings`:
+* `outline`
+
+In `prioritized_strings` add `outline`. If you want to use colored icons, in `prioritized_strings` add `color`, too. Otherwise set `filter="color"`.
+
+Press `Match Icons to Inputs`.
+
+Validate the results by inspecting the `matching_icons` dictionary.
+
+## Kenny Input Prompts Pixel 16x
+
+Incompatible: File names not useable.
+
+## Xelu 's Free Controller & Key Prompts
+
+
+Available from [thoseawesomeguys.com](https://thoseawesomeguys.com/prompts/).
+
+This pack is organized by `Device`. In the inspector of `InputIconMapper`, set the `directories` to include the subdirectories of the asset pack. Assumes using the `Dark` icon set with the keyboard and mouse.
+* `.../Xelu_Free_Controller&Key_Prompts/Keyboard & Mouse/Dark`
+* `.../Xelu_Free_Controller&Key_Prompts/Xbox Series`
+* `.../Xelu_Free_Controller&Key_Prompts/PS5`
+* `.../Xelu_Free_Controller&Key_Prompts/Switch`
+* `.../Xelu_Free_Controller&Key_Prompts/Steam Deck`
+
+Under the `FileLister` properties of the `InputIconMapper`, expand the `Constraints` and `Advanced Search` tabs. Set `ends_with=".png"`.
+
+Press `Refresh Files`.
+
+Set `filtered_strings` to:
+* `dark`
+* `key`
+
+Set `replace_strings` with the key pairs:
+* `"Ps 5": "Playstation"`
+* `"Xbox Series X": "Xbox"`
+* `"Steam Deck": "Steamdeck"`
+* `"L 1": "Left Shoulder"`
+* `"R 1": "Right Shoulder"`
+* `"L 2": "Left Trigger"`
+* `"R 2": "Right Trigger"`
+* `"Click": "Press"`
+
+Set `add_stick_directions=true`.
+
+Press `Match Icons to Inputs`.
+
+Validate the results by inspecting the `matching_icons` dictionary.
+
+Since `Generic` device icons are not available, set `initial_joypad_device` to either `Xbox`, `Playstation`, `Switch`, or `Steamdeck`.
+
+## Free Icon Pack for Unity & Unreal – 1500+ Input Icons for Game UI
+
+
+Available from [itch.io](https://juliocacko.itch.io/free-input-prompts).
+
+This pack is organized by `Device/IconType`. In the inspector of `InputIconMapper`, set the `directories` to include the subdirectories of the asset pack. Assumes using the `Dark` icon set with the keyboard and mouse, and `Default` for the others.
+* `.../Source/Keyboard_Mouse/Dark`
+* `.../Source/P4Gamepad/Default`
+* `.../Source/XGamepad/Default`
+* `.../Source/SGamepad/Default`
+
+Under the `FileLister` properties of the `InputIconMapper`, expand the `Constraints` and `Advanced Search` tabs. Set `ends_with=".png"`.
+
+Press `Refresh Files`.
+
+In `prioritized_strings`, add either `color` or `white`, depending on what icons you prefer.
+
+Set `filtered_strings` to:
+* `dark`
+* `key`
+* `t`
+* `color`
+* `white`
+
+Set `replace_strings` with the key pairs:
+* `"P 4": "Playstation"`
+* `"X": "Xbox"`
+* `"S": "Switch"`
+* `"L": "Left Stick"`
+* `"R": "Right Stick"`
+* `"Left Stick 1": "Left Shoulder"`
+* `"Right Stick 1": "Right Shoulder"`
+* `"Left Stick 2": "Left Trigger"`
+* `"Right Stick 2": "Right Trigger"`
+
+Press `Match Icons to Inputs`.
+
+Validate the results by inspecting the `matching_icons` dictionary.
+
+Since `Generic` device icons are not available, set `initial_joypad_device` to either `Xbox`, `Playstation`, or `Switch`.
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/JoypadInputs.md b/evolve-die-repeat/addons/maaacks_game_template/docs/JoypadInputs.md
new file mode 100644
index 0000000..0515080
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/JoypadInputs.md
@@ -0,0 +1,31 @@
+# Joypad Inputs
+
+This page covers topics related to working with joypads.
+
+## Recognized Devices
+
+- Xbox
+- Playstation 4
+- Playstation 5
+
+### Unconfirmed
+
+- Switch
+- Steam Deck
+
+## Added UI Inputs
+
+There is a `override.cfg` in the project root directory that adds a few additional inputs to the project's built-in UI actions.
+
+These additional inputs are for joypads and include the following:
+
+- `UI Accept`: A Button (Xbox A / Sony X)
+- `UI Cancel`: Back Button (Xbox Back / Sony Select)
+- `UI Page Up`: Left Shoulder (Xbox LB / Sony L1)
+- `UI Page Down`: Right Shoulder (Xbox RB / Sony R2)
+
+However, for these to work in exported versions of the project, the inputs need to either be added manually to the project's built-in actions, or `override.cfg` will need to be included in the exports. The latter can be done by including the pattern (`*.cfg`) in **Filters to export non-resource files/folders** under the *Resources* tab of the *Export* window.
+
+## Web Builds
+
+Godot (or the template) currently does not support joypad device detection on the web. If icons are being used for input remapping, the joypad icons will *not* update automatically to match a new detected controller.
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/LoadingScenes.md b/evolve-die-repeat/addons/maaacks_game_template/docs/LoadingScenes.md
new file mode 100644
index 0000000..7a688c4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/LoadingScenes.md
@@ -0,0 +1,64 @@
+# Loading Scenes
+
+These are instructions for using the `SceneLoader` autoload to load resources asynchronously. This is especially useful for large scenes, but can be used throughout a project. The plugin comes with an example loading screen as well.
+
+## Foreground Loading
+
+By default, calling `SceneLoader.load_scene(path_to_scene)` will unload the current scene and replace it with a loading screen, until the next scene is fully loaded. At that point, the loading screen will be removed and replaced with the next scene.
+
+It is intended to replace calls to `get_tree().change_scene_to_file(path_to_scene)` that open large scenes and may cause a stutter. It can also replace calls to `get_tree().change_scene_to_packed(loaded_packed_scene)`, that may require large scenes to already be loaded into memory.
+
+## Background Loading
+
+Calling `SceneLoader.load_scene(path_to_scene, true)` will load the scene in the background (hence the `in_background` argument being set to `true`).
+
+There are a number of ways to then show the scene when it is ready, or even switch to foreground loading in case it is not.
+
+### Signal When Loaded
+
+A scene can be loaded as soon as it is ready, by listening for the `scene_loaded` signal from the `SceneLoader`.
+
+An example of this is in `level_loader.gd` of [Maaack's Game Template](https://github.com/Maaack/Godot-Game-Template/blob/main/addons/maaacks_game_template/extras/scripts/level_loader.gd), which loads scenes in the background and displays a loading screen, but doesn't change the whole scene when the next one is ready. Instead, it loads the next level into a container.
+
+
+Below is an example of reacting to `SceneLoader` signals to open the loaded scene in an optional `container` node, or switch to it entirely.
+
+```
+SceneLoader.load_scene(path_to_scene, true)
+await SceneLoader.scene_loaded
+if container:
+ # Has a container, so will open the loaded scene in it
+ var resource = SceneLoader.get_resource()
+ var instance = resource.instantiate()
+ container.add_child(instance)
+else:
+ # Has no container, so will switch to the loaded scene
+ SceneLoader.change_scene_to_resource()
+```
+
+### On User Input or a Timed Event
+
+A scene could load the next scene based on a timer, or when the player indicates that they are ready.
+
+An example is in `opening.gd` of [Maaack's Game Template](https://github.com/Maaack/Godot-Game-Template/blob/main/addons/maaacks_game_template/base/nodes/opening/opening.gd), which starts loading the main menu immediately, and switches to it when its animations finish. Player's input can speed them up the animations, so by the end, if the next scene is not ready, a loading screen can be shown instead.
+
+Below is an example of starting the load of the next scene.
+
+```
+func _ready() -> void:
+ # Immediately starting to load the next scene in the background
+ SceneLoader.load_scene(path_to_scene, true)
+```
+
+Below is an example of reacting to the player's input to either show the next scene or a loading screen.
+
+```
+func _unhandled_input(event : InputEvent) -> void:
+ var status = SceneLoader.get_status()
+ if status != ResourceLoader.THREAD_LOAD_LOADED:
+ # Resource is not loaded, so show the loading screen
+ SceneLoader.change_scene_to_loading_screen()
+ else:
+ # Resource is loaded, so switch to the loaded scene
+ SceneLoader.change_scene_to_resource()
+```
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/MainMenuSetup.md b/evolve-die-repeat/addons/maaacks_game_template/docs/MainMenuSetup.md
new file mode 100644
index 0000000..6311d3b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/MainMenuSetup.md
@@ -0,0 +1,53 @@
+# Main Menu Setup
+
+These are instructions for editing the main menu.
+
+## Selecting a Menu
+The main menu by default is `main_menu_with_animations.tscn`. The path to the main menu is set in the `AppConfig` autoload. Open the `app_config.tscn` scene, inspect the root note, and edit the `Main Menu Scene Path` to the desired scene.
+
+Alternatively, the path to the main menu can be set directly in the following scenes:
+
+- `opening.tscn`
+- `pause_menu_layer.tscn`
+- `game_ui.tscn` (`level_manager.gd`)
+- `end_credits.tscn`
+
+> [!IMPORTANT]
+> Animations from 4.6 are not fully backwards compatible.
+> When running, `main_menu_with_animations.tscn` will show a gray screen for Godot versions < 4.6.
+> It is recommended to use the latest version of Godot, or use `main_menu.tscn` instead of `main_menu_with_animations.tscn`.
+
+## Clear Inheritance
+
+Most example scenes in the template inherit from scenes in `addons`. Nodes inherited from a parent scene are highlighted in yellow (by default) in the scene tree. Inherited nodes cannot be edited like native nodes. Therefore, it is recommended to first right-click on the root node, and select `Clear Inheritance`. You'll get a warning that this cannot be undone, but it's okay. The inheritance is useful when developing the plugin itself, but much less so for a game.
+
+## Title and Subtitle
+
+The title will automatically update from the project's name. If a custom title is desired, select the `TitleLabel` node, set `Auto Update` to false, and set `Text` to the custom title. The `SubTitleLabel` can be customized with the `Text` field as well, or hidden entirely.
+
+## Visual Placement
+
+The positions and anchor presets of the UI elements can be adjusted to match most designs with ease. Buttons can be centered, right or left justfied, or arranged horizontally. Most visual UI elements are contained within `MarginContainer` and `Control` nodes that allow for fine-tuning of placement.
+
+## Scene Structure
+Some designs may require rearranging the nodes in the scene tree. This is easier once the inheritance to the parent scene is cleared. However, if editing `main_menu_with_animations.tscn`, keep in mind that there are animations, and moving elements outside of the animated containers may have undesired effects.
+
+## 3D Background
+If adding a 3D background to the menu, a 3D world node in the scene tree should normally display behind the control nodes. Using a `SubViewport` with the 3D world node attached to that adds a degree of control over scaling. Adding that into a `SubViewportContainer` provides even more fine-tune control of layering and makes it easy to add a texture shader to the whole background.
+
+## Level Select
+
+A basic level select scene is available to add to the menu. In `main_menu_with_animations.tscn`, click the root `MainMenu` mode and set `Level Select Packed Scene` to `level_select_menu.tscn`. The button will appear on the main menu when the player has reached the second level.
+
+Levels can be added to the menu by inspecting the `SceneLister` and either selecting a directory to automatically read scene files from, or populating the files array manually.
+
+## Theming
+It is recommended to have a custom theme for a project. Create a theme resource file or use one of the ones provided with the template and set it as the custom theme in the project settings. Any changes made to the theme file will then apply automatically to the whole project.
+
+The main UI elements that are used throughout the project that require theming for customization are:
+- Button
+- Label
+- PanelContainer
+- ProgressBar
+- TabContainer
+- Tree
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/MovingFiles.md b/evolve-die-repeat/addons/maaacks_game_template/docs/MovingFiles.md
new file mode 100644
index 0000000..fd46220
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/MovingFiles.md
@@ -0,0 +1,35 @@
+# Moving Files
+
+This page covers some tips for rearranging files to an individual developer's preference.
+
+> [!WARNING]
+> Backup your project before attempting to rearrange files.
+> You assume any risk.
+
+## Move Files in the Editor
+
+Use the editor to move files around, as this makes sure that `.uid` files get moved with `.gd` files, external resource references will get updated in `.tscn` files, and paths in project settings get updated.
+
+UIDs do help with moving files outside of the editor, but not all scenes will have UIDs set if they've just recently been copied from the examples.
+
+## Update File Paths
+
+The flow of scenes in the template by default goes `Opening -> Main Menu -> Game Scene -> Ending Scene`.
+
+The `Opening` is referenced in the project settings, and will get automatically update if moved in the editor.
+
+The rest have their default paths stored in the `AppConfig` autoload. These do not get automatically updated, so the developer must update these paths if they change.
+
+Alternatively, the developer can specify paths in the scenes that reference the other scenes by path. These include:
+* `opening.tscn`
+* `main_menu.tscn`
+* `main_menu_with_animations.tscn`
+* `pause_menu_layer.tscn`
+* `game_ui.tscn` (`level_manager.gd`)
+* `end_credits.tscn`
+
+Any file paths in these scenes left blank will default to the values in `AppConfig`.
+
+## Internal Details
+
+File paths, stored as strings, do not get automatically updated by the editor when their target moves. Paths are used when asynchronous loading of scenes (ie. using `SceneLoader`) is preferred, primarily for memory management.
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/NewProject.md b/evolve-die-repeat/addons/maaacks_game_template/docs/NewProject.md
new file mode 100644
index 0000000..5ed212a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/NewProject.md
@@ -0,0 +1,66 @@
+# New Projects
+
+> [!WARNING]
+> This page is being deprecated in favor of [Basic Setup](/addons/maaacks_game_template/docs/BasicSetup.md).
+
+These instructions assume starting with the entire contents of the project folder. This will be the case when cloning the repo, or starting from the *template* version in the Godot Asset Library accessible from the Project Manager window.
+
+
+1. Finish setup.
+
+ 1. Delete duplicate example files.
+ 1. Go to `Project > Tools > Run Maaack's Game Template Setup...`.
+ 2. In the `Setup Wizard` window next to "Delete Example Files", click `Run`.
+ 3. In the next window, select `Yes` to continue with removing the example files.
+
+ 2. Update autoload file paths.
+ 1. Go to `Project > Tools > Run Maaack's Game Template Setup...`.
+ 2. In the `Setup Wizard` window next to "Update Autoload Paths", click `Run`.
+
+ 3. Set a default theme.
+ 1. Go to `Project > Tools > Run Maaack's Game Template Setup...`.
+ 2. In the `Setup Wizard` window next to "Set the Default Theme", click `Run`.
+ 3. In the next window, select the desired theme from the preview and select `Yes` to set it as the project's default theme.
+
+2. Update the project’s name.
+
+
+ 1. Go to `Project > Project Settings… > General > Application > Config`.
+ 2. Update `Name` to `"Game Name"`.
+ 3. Close the window.
+ 4. Open `main_menu_with_animations.tscn`.
+ 5. Select the `TitleLabel` node.
+ 6. The `Text` should match the project's name.
+ 1. If `Text` is customized, set `Auto Update` to false.
+ 7. Select the `SubtitleLabelNode` node and customize the `Text` as desired.
+ 8. Save the scene.
+
+
+3. Add / remove configurable settings to / from menus.
+
+
+ 1. Open `mini_options_menu.tscn` or `[audio|visual|input|game]_options_menu.tscn` scenes to edit their options.
+ 2. If an option is not desired, it can always be hidden, or removed entirely (sometimes with some additional work).
+ 3. If a new option is desired, refer to [Adding Custom Options.](/addons/maaacks_game_template/docs/AddingCustomOptions.md)
+
+
+4. Update the game credits / attribution.
+
+
+ 1. Update the example `ATTRIBUTION.md` with the project's credits.
+ 2. Open `credits_label.tscn`.
+ 3. Check the `CreditsLabel` has updated with the text.
+ 4. Optionally, disable `Auto Update` and customize the text.
+ 5. Save the scene (even if it shows no changes).
+
+
+5. Continue with:
+
+ 1. [Setting up the Main Menu.](/addons/maaacks_game_template/docs/MainMenuSetup.md)
+ 2. [Setting up a Game Scene.](/addons/maaacks_game_template/docs/GameSceneSetup.md)
+ 3. [Loading scenes asynchronously.](/addons/maaacks_game_template/docs/LoadingScenes.md)
+ 4. [Adding icons to the Input Options.](/addons/maaacks_game_template/docs/InputIconMapping.md)
+ 5. [Blending Music.](/addons/maaacks_game_template/docs/BlendingMusic.md)
+ 6. [Adding UI Sound Effects.](/addons/maaacks_game_template/docs/AddingUISFX.md)
+ 7. [Adding Custom Options.](/addons/maaacks_game_template/docs/AddingCustomOptions.md)
+ 8. [Utilizing Game Saving.](/addons/maaacks_game_template/docs/GameSaving.md)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/OptionsMenuSetup.md b/evolve-die-repeat/addons/maaacks_game_template/docs/OptionsMenuSetup.md
new file mode 100644
index 0000000..08483b0
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/OptionsMenuSetup.md
@@ -0,0 +1,65 @@
+# Options Menu Setup
+
+These instructions cover customizing the options menus.
+
+User choices persist in a user config file, and are loaded when the app opens.
+
+## Removing Options
+
+By default, more options are provided than are generally needed. It is recommended to hide or remove the extras.
+
+1. Open `master_options_menu_with_tabs.tscn`.
+2. Delete nodes of option scenes that do not apply to the game.
+ 1. `Controls` is usually useful for supporting input remapping.
+ 2. `Inputs` can be removed unless supporting a 3D camera.
+ 3. `Audio` is usually useful.
+ 4. `Video` is usually useful.
+ 5. `Game` can be removed unless supporting persistant game state.
+3. Open `mini_options_menu.tscn` or `[audio|visual|input|game]_options_menu.tscn` scenes to edit their options.
+4. If an individual option is not desired, it can be hidden or removed entirely (sometimes with some additional work).
+
+## Adding Options
+
+New buttons, sliders, or editable text fields can be added that automatically persist user choices between sessions.
+
+### To the Menu
+Custom options can be added to a menu without any code.
+
+1. Add an `option_control.tscn` node as a child to a container in a scene.
+ 1. `slider_option_control.tscn` or `toggle_option_control.tscn` can be used if those types match requirements. In that case, skip step 6.
+ 2. `list_option_control.tscn` and `vector_2_list_option_control.tscn` are also available, but more complicated. See the `ScreenResolution` example.
+3. Select the `OptionControl` node just added, to edit it in the inspector.
+4. Add an `Option Name`. This prefills the `Key` string.
+5. Select an `Option Section`. This prefills the `Section` string.
+6. Add any kind of `Button`, `Slider`, `LineEdit`, or `TextEdit` to the `OptionControl` node.
+7. Save the scene.
+
+### To the Game
+For options to have any effect outside of the menus, they will need to be referenced by their `key` and `section` from the `PlayerConfig` class.
+```
+PlayerConfig.get_config(key, section)
+```
+
+For example, here is how to get the player's desired input sensitivity for controlling a player camera.
+```
+var mouse_sensitivity : float = PlayerConfig.get_config(AppSettings.INPUT_SECTION, "MouseSensitivity", 1.0)
+var joypad_sensitivity : float = PlayerConfig.get_config(AppSettings.INPUT_SECTION, "JoypadSensitivity", 1.0)
+```
+
+### Validation
+Validate the values being stored in your local `player_config.cfg` file.
+1. Navigate to `Project > Open User Data Folder`.
+2. Open `player_config.cfg`.
+3. Find the section by the section name in brackets, and the key name followed by an equals.
+
+For example, here is how the player's desired input sensitivity could appear in the config file.
+
+```
+[InputSettings]
+
+MouseSensitivity=1.05
+JoypadSensitivity=0.95
+```
+
+> [!NOTE]
+> Some settings may not appear until they have been customized.
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/PluginSuite.md b/evolve-die-repeat/addons/maaacks_game_template/docs/PluginSuite.md
new file mode 100644
index 0000000..10d9672
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/PluginSuite.md
@@ -0,0 +1,25 @@
+# Plugin Suite
+
+
+
+Maaack's Game Templates are a culmination of a suite of plugins, that can be downloaded individually, if desired.
+
+## Hierarchy
+
+- Game Template ( [Github](https://github.com/Maaack/Godot-Game-Template) | [Asset Library](https://godotengine.org/asset-library/asset/2709) )
+ - Menus Template ( [Github](https://github.com/Maaack/Godot-Menus-Template) | [Asset Library](https://godotengine.org/asset-library/asset/2899) )
+ - Options Menus ( [Github](https://github.com/Maaack/Godot-Options-Menus) | [Asset Library](https://godotengine.org/asset-library/asset/3058) )
+ - Input Remapping ( [Github](https://github.com/Maaack/Godot-Input-Remapping) | [Asset Library](https://godotengine.org/asset-library/asset/4051) )
+ - Scene Loader ( [Github](https://github.com/Maaack/Godot-Scene-Loader) | [Asset Library](https://godotengine.org/asset-library/asset/2896) )
+ - Credits Scene ( [Github](https://github.com/Maaack/Godot-Credits-Scene) | [Asset Library](https://godotengine.org/asset-library/asset/2932) )
+ - UI Sound Controller ( [Github](https://github.com/Maaack/Godot-UI-Sound-Controller) | [Asset Library](https://godotengine.org/asset-library/asset/2897) )
+ - Music Controller ( [Github](https://github.com/Maaack/Godot-Music-Controller) | [Asset Library](https://godotengine.org/asset-library/asset/2898) )
+- Minimal Game Template ( [Github](https://github.com/Maaack/Godot-Minimal-Game-Template) | [Asset Library](https://godotengine.org/asset-library/asset/4657) )
+ - Options Menus ( [Github](https://github.com/Maaack/Godot-Options-Menus) | [Asset Library](https://godotengine.org/asset-library/asset/3058) )
+ - Input Remapping ( [Github](https://github.com/Maaack/Godot-Input-Remapping) | [Asset Library](https://godotengine.org/asset-library/asset/4051) )
+
+
+## Overview
+
+[](https://youtu.be/3yzaUSaROhw)
+
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/Screenshots.md b/evolve-die-repeat/addons/maaacks_game_template/docs/Screenshots.md
new file mode 100644
index 0000000..0d3bbc1
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/Screenshots.md
@@ -0,0 +1,86 @@
+# Screenshots
+
+1280x720 and 640x360 resolutions are shown, and resolutions up to 4k are supported.
+
+## 1280 X 720
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 640 x 360
+
+Screenshots organized by included themes.
+
+### Default (No Theme)
+
+
+
+
+
+
+
+
+
+
+### Gravity
+
+
+
+
+
+
+
+
+
+
+
+### Lore
+
+
+
+
+
+
+
+
+### Steal This Theme
+
+
+
+
+
+
+
+
+
+
+
+
+### Tower
+
+
+
+
+
+
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/UpdatingCredits.md b/evolve-die-repeat/addons/maaacks_game_template/docs/UpdatingCredits.md
new file mode 100644
index 0000000..dd6d647
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/UpdatingCredits.md
@@ -0,0 +1,32 @@
+# Updating Credits
+
+These instructions cover updating the credits / attribution, both in the repo and application.
+
+Credits are parsed automatically from the `ATTRIBUTION.md` file included with the template.
+
+## Steps
+
+1. Open `ATTRIBUTION.md` in a text or markdown editor.
+2. Update the file with the project's credits. Refer to [Format Examples](#format-examples).
+3. Save the changes and close the file.
+4. Open `credits_label.tscn`.
+5. Check the `CreditsLabel` has updated with the text.
+ 1. Optionally, disable `Auto Update` and customize the text.
+6. Save the scene (even if it shows no changes).
+
+### Alternative
+Optionally, `*.md` can be included for export in the _Export_ window's `Resources` tab, and steps 4-6 can be skipped, as `credits_label.tscn` will continue to update from `ATTRIBUTION.md` when exported.
+
+## Format Examples
+### Collaborators
+
+> ### Role
+> Contributor name
+> [Contributor name w/ link]()
+
+### Tools or Sourced Assets
+> ### [Asset Type]
+> #### Tool Name/Asset Name/Use Case
+> Author: [Name]()
+> Source: [Domain : webpage.html]()
+> License: [License]()
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/UploadingToItchIo.md b/evolve-die-repeat/addons/maaacks_game_template/docs/UploadingToItchIo.md
new file mode 100644
index 0000000..8c85240
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/UploadingToItchIo.md
@@ -0,0 +1,40 @@
+# Uploading to itch.io
+
+This is a guide on using _Butler_ along with a _Butler Manager_ helper script to rapidly upload and deploy your builds to itch.io. It's useful for game jams!
+
+## Butler
+
+_Butler_ is a command-line tool provided by itch.io to upload content to project pages on itch.io.
+
+Get it here: https://itchio.itch.io/butler
+
+After installing it, run `butler login` and go through the login flow. You should only have to do this once.
+
+_Butler_ automatically compares builds and only uploads what has changed, so the first upload will take the longest, but every upload after should be faster.
+
+## Exporting
+
+It is recommended to create an `exports/` directory for your builds, add the directory to your `.gitignore` file (if applicable), and also add a `.gdignore` file to the directory to avoid having Godot add `*.import` files to it as well.
+
+## Butler Manager
+
+This script provided at `addons/maaacks_game_template/extras/scripts/butler_manager.sh` can be used to rapidly deploy 4 different builds to your project page. Make sure you can run `bash` shell scripts on your OS. Copy the script into your `exports/` directory and mark it as an executable, if required.
+
+Run the script with `./butler_manager.sh`. On the first run, it will ask for the destination for uploads. This is a combination of the page owner and the project's URL.
+
+The Butler Manager will look for directories named the following:
+
+- HTML5
+- Linux
+- Windows
+- MacOS
+
+Matching directories will be uploaded by _Butler_ to their corresponding channels on itch.io. They will then be processed by itch.io servers and eventually appear on the page (usually within 2 minutes).
+
+The owner of the project page will also get a notification when the builds have finished processing.
+
+You can re-run `./butler_manager.sh` right after an export from Godot to keep your builds synced.
+
+## Automating export and publication
+
+You can use Github Actions to automate these steps. Look into the `.git/workflows` folder [and this guide](./BuildAndPublish.md).
diff --git a/evolve-die-repeat/addons/maaacks_game_template/docs/Videos.md b/evolve-die-repeat/addons/maaacks_game_template/docs/Videos.md
new file mode 100644
index 0000000..320ce44
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/docs/Videos.md
@@ -0,0 +1,14 @@
+# Videos
+
+## Tutorials
+
+[](https://youtu.be/U9CB3vKINVw)
+[](https://youtu.be/-QWJnZ8bVdk)
+[](https://youtu.be/DE_6kqvT_yc)
+[](https://youtu.be/3yzaUSaROhw)
+[](https://youtu.be/SBE4icfXYRA)
+[](https://youtu.be/wCc2QUnaBKo)
+
+## Events
+
+[](https://youtu.be/nUOAzSNmz1A)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/asset_checker.sh b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/asset_checker.sh
new file mode 100644
index 0000000..4174191
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/asset_checker.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+# asset checker command
+# Used for quickly checking that assets (like audio files) are being used where expected.
+#
+# Recursively searches through scene files (.tscn, .scn, .res)
+# for occurrences of asset types (default: AudioStream).
+# It then outputs the paths of assets discovered,
+# along with the file names that use them.
+
+short_flag=false
+asset_type="AudioStream"
+
+print_usage() {
+ printf "Usage: -sa %s\n" "$asset_type"
+}
+
+while getopts 'a:s' flag; do
+ case "${flag}" in
+ a)
+ asset_type="${OPTARG}"
+ ;;
+ s)
+ short_flag=true
+ ;;
+ *)
+ print_usage
+ exit 1
+ ;;
+ esac
+done
+
+# Initialize an associative array to store paths and corresponding files
+declare -A path_files
+
+while IFS=: read -r file line; do
+ path=$(echo "$line" | grep -o 'path="[^"]*' | cut -d'"' -f2)
+ if [ -n "$path" ]; then
+ # Append the current file to the string of files for this path
+ # Note: Bash does not support having arrays as values of associative array.
+ # Using a pipe `|` separator instead, and then splitting on output
+ if [ -z "${path_files["$path"]}" ]; then
+ path_files["$path"]=$file
+ else
+ path_files["$path"]+="|$file"
+ fi
+ fi
+done < <(egrep -ir --include=*.{tscn,scn,res} "type=\"$asset_type\"")
+
+# Get the paths and sort them
+sorted_paths=()
+for key in "${!path_files[@]}"; do
+ sorted_paths+=("$key")
+done
+IFS=$'\n' sorted_paths=($(sort <<< "${sorted_paths[*]}"))
+unset IFS
+
+# Print out the results
+for path in "${sorted_paths[@]}"; do
+ # Note: Bash does not support having arrays as values of associative array.
+ # Splitting the concatenated files string on the pipe `|` separator.
+ IFS='|' read -r -a files_array <<< "${path_files[$path]}"
+ files_count=${#files_array[@]}
+ printf "%-80s | Uses: %s\n" "$path" "$files_count"
+ if ! $short_flag ; then
+ for file in "${files_array[@]}"; do
+ printf "\t%82s\n" "$file"
+ done
+ echo
+ fi
+done
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/build-and-publish.yml b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/build-and-publish.yml
new file mode 100644
index 0000000..dfa36bd
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/build-and-publish.yml
@@ -0,0 +1,281 @@
+# MIT License
+
+# Copyright (c) 2018 BARICHELLO
+
+# 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.
+
+# Original file: https://github.com/abarichello/godot-ci/blob/master/.github/workflows/godot-ci.yml
+# This edited version is triggered using a Github Release and uploads artifacts to the release.
+# Furthermore, a new job (upload to itch.io using butler) has been added.
+# Modified by: Nicolas Oulianov (github @oulianov) & Marek Belski
+
+name: "Build and Deploy to Itch.io"
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+permissions:
+ # Allow release asset uploads
+ contents: write
+
+env:
+ # Set this repository variable to the version to build with (>=4)
+ # This will be used for all container images: barichello/godot-ci:[#.#]
+ # Look at available versions here: https://hub.docker.com/r/barichello/godot-ci/tags
+ GODOT_VERSION: ${{ vars.GODOT_VERSION }}
+ # Set this repository variable to your game name (it will be the filename downloaded)
+ EXPORT_NAME: ${{ vars.EXPORT_NAME }}
+ # NOTE: If your `project.godot` is at the repository root, set `PROJECT_PATH` to "."
+ # If it's in a subdirectory, set it to the subdirectory name (e.g., "your-game")
+ PROJECT_PATH: .
+
+jobs:
+ export-web:
+ name: Web Export
+ runs-on: ubuntu-24.04
+ container:
+ image: barichello/godot-ci:${{ vars.GODOT_VERSION }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Setup
+ run: |
+ mkdir -v -p ~/.local/share/godot/export_templates/
+ mkdir -v -p ~/.config/
+ mv /root/.config/godot ~/.config/godot || true
+ mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable || true
+
+ - name: Web Build
+ run: |
+ mkdir -v -p build/web
+ EXPORT_DIR="$(readlink -f build)"
+ cd $PROJECT_PATH
+ godot --headless --verbose --export-release "Web" "$EXPORT_DIR/web/index.html"
+
+ - name: Prepare web release asset (zip)
+ run: |
+ # ensure zip is available in the container
+ if ! command -v zip >/dev/null 2>&1; then
+ apt-get update && apt-get install -y zip
+ fi
+ # Change to the web directory and zip its contents directly
+ cd build/web
+ zip -r ../${EXPORT_NAME}-web.zip .
+ ls -lh ../${EXPORT_NAME}-web.zip
+ shell: bash
+
+ - name: Upload to GitHub Release (if this run is a release)
+ if: ${{ github.event_name == 'release' }}
+ uses: svenstaro/upload-release-action@v2
+ with:
+ file: build/${{ env.EXPORT_NAME }}-web.zip
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: web
+ path: build/web
+
+ export-windows:
+ name: Windows Export
+ runs-on: ubuntu-24.04
+ container:
+ image: barichello/godot-ci:${{ vars.GODOT_VERSION }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Setup
+ run: |
+ mkdir -v -p ~/.local/share/godot/export_templates/
+ mkdir -v -p ~/.config/
+ mv /root/.config/godot ~/.config/godot || true
+ mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable || true
+
+ - name: Windows Build
+ run: |
+ mkdir -v -p build/windows
+ EXPORT_DIR="$(readlink -f build)"
+ cd $PROJECT_PATH
+ godot --headless --verbose --export-release "Windows Desktop" "$EXPORT_DIR/windows/${EXPORT_NAME}.exe"
+
+ - name: Archive Build
+ run: |
+ # ensure zip is available in the container
+ if ! command -v zip >/dev/null 2>&1; then
+ apt-get update && apt-get install -y zip
+ fi
+ # Change to the web directory and zip its contents directly
+ cd build/windows
+ zip -r ../${EXPORT_NAME}-windows.zip .
+ ls -lh ../${EXPORT_NAME}-windows.zip
+ shell: bash
+
+ - name: Upload to GitHub Release (if this run is a release)
+ if: ${{ github.event_name == 'release' }}
+ uses: svenstaro/upload-release-action@v2
+ with:
+ file: build/${{ env.EXPORT_NAME }}-windows.zip
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: windows
+ path: build/${{ env.EXPORT_NAME }}-windows.zip
+
+ export-linux:
+ name: Linux Export
+ runs-on: ubuntu-24.04
+ container:
+ image: barichello/godot-ci:${{ vars.GODOT_VERSION }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Setup
+ run: |
+ mkdir -v -p ~/.local/share/godot/export_templates/
+ mkdir -v -p ~/.config/
+ mv /root/.config/godot ~/.config/godot || true
+ mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable || true
+
+ - name: Linux Build
+ run: |
+ mkdir -v -p build/linux
+ EXPORT_DIR="$(readlink -f build)"
+ cd $PROJECT_PATH
+ godot --headless --verbose --export-release "Linux" "$EXPORT_DIR/linux/${EXPORT_NAME}.x86_64"
+
+ - name: Archive Build
+ run: |
+ # ensure zip is available in the container
+ if ! command -v zip >/dev/null 2>&1; then
+ apt-get update && apt-get install -y zip
+ fi
+ # Change to the web directory and zip its contents directly
+ cd build/linux
+ zip -r ../${EXPORT_NAME}-linux.zip .
+ ls -lh ../${EXPORT_NAME}-linux.zip
+ shell: bash
+
+ - name: Upload to GitHub Release (if this run is a release)
+ if: ${{ github.event_name == 'release' }}
+ uses: svenstaro/upload-release-action@v2
+ with:
+ file: build/${{ env.EXPORT_NAME }}-linux.zip
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: linux
+ path: build/${{ env.EXPORT_NAME }}-linux.zip
+
+ export-mac:
+ name: macOS Export
+ runs-on: ubuntu-24.04
+ container:
+ image: barichello/godot-ci:${{ vars.GODOT_VERSION }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ lfs: true
+
+ - name: Setup
+ run: |
+ mkdir -v -p ~/.local/share/godot/export_templates/
+ mkdir -v -p ~/.config/
+ mv /root/.config/godot ~/.config/godot || true
+ mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable || true
+
+ - name: Mac Build
+ run: |
+ mkdir -v -p build/mac
+ EXPORT_DIR="$(readlink -f build)"
+ cd $PROJECT_PATH
+ godot --headless --verbose --export-release "macOS" "$EXPORT_DIR/mac/${EXPORT_NAME}-mac.zip"
+
+ - name: Upload to GitHub Release (if this run is a release)
+ if: ${{ github.event_name == 'release' }}
+ uses: svenstaro/upload-release-action@v2
+ with:
+ file: build/mac/${{ env.EXPORT_NAME }}-mac.zip
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: mac
+ path: build/mac
+
+ deploy-to-itch:
+ name: Deploy to Itch.io
+ needs: [export-web, export-windows, export-linux, export-mac]
+ runs-on: ubuntu-24.04
+ env:
+ BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }} # Create this SECRET on Github
+ ITCH_USERNAME: ${{ vars.ITCH_USERNAME }} # Create this VARIABLE on Github
+ ITCH_GAME: ${{ vars.ITCH_GAME }} # Create this VARIABLE on Github
+ steps:
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: builds
+
+ - name: Install Butler
+ run: |
+ curl -L -o butler.zip https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default
+ unzip butler.zip
+ chmod +x butler
+ ./butler -V
+
+ - name: Get version from tag or set default
+ id: version
+ run: |
+ if [ "${{ github.event_name }}" = "release" ]; then
+ VERSION="${{ github.event.release.tag_name }}"
+ else
+ VERSION="dev-${{ github.run_number }}"
+ fi
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Deploying version: $VERSION"
+
+ - name: Upload to Itch.io - Web
+ run: |
+ ./butler push builds/web ${{ env.ITCH_USERNAME }}/${{ env.ITCH_GAME }}:html5 --userversion "${{ steps.version.outputs.version }}"
+
+ - name: Upload to Itch.io - Windows
+ run: |
+ ./butler push builds/windows ${{ env.ITCH_USERNAME }}/${{ env.ITCH_GAME }}:windows --userversion "${{ steps.version.outputs.version }}"
+
+ - name: Upload to Itch.io - Linux
+ run: |
+ ./butler push builds/linux ${{ env.ITCH_USERNAME }}/${{ env.ITCH_GAME }}:linux --userversion "${{ steps.version.outputs.version }}"
+
+ - name: Upload to Itch.io - macOS
+ run: |
+ ./butler push builds/mac ${{ env.ITCH_USERNAME }}/${{ env.ITCH_GAME }}:mac --userversion "${{ steps.version.outputs.version }}"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/butler_manager.sh b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/butler_manager.sh
new file mode 100644
index 0000000..b576487
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/butler_manager.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# butler manager command
+# Uploads directories as builds to matching itch.io channels.
+# HTML5 => html5
+# Linux => linux
+# Windows => win
+# MacOS => osx
+
+file=upload_destination.txt
+directories=("HTML5" "Linux" "Windows" "MacOS")
+channels=("html5" "linux" "win" "osx")
+
+# Check if the file exists
+if [ ! -e $file ]; then
+ # File doesn't exist, create an empty one
+ touch $file
+fi
+
+# File exists, read the first line into a variable
+read -r destination < $file
+
+if [ -z "$destination" ]; then
+ # File is empty, prompt the user for input
+ echo "Please enter the build destination (username/project-url-after-slash)."
+ read -r user_input
+
+ # Save user input to the file
+ echo "$user_input" > "$file"
+ echo "Destination saved to $file."
+ destination="$user_input"
+fi
+
+# Check for the existence of directories and upload contents
+for ((i=0; i<${#directories[@]}; i++)); do
+ dir="${directories[i]}"
+ channel="${channels[i]}"
+
+ if [ -d "$dir" ]; then
+ echo butler push ./$dir/ $destination:$channel
+ butler push ./$dir/ $destination:$channel
+ else
+ echo "Directory '$dir' does not exist."
+ fi
+done
\ No newline at end of file
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/capture_mouse.gd b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/capture_mouse.gd
new file mode 100644
index 0000000..cf6ce5b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/capture_mouse.gd
@@ -0,0 +1,8 @@
+extends Control
+## Control node that captures the mouse for games that require it.
+##
+## Used for games that use the mouse to move the camera (ex. FPS or third-person shooters).
+
+func _gui_input(event):
+ if event is InputEventMouseButton and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/capture_mouse.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/capture_mouse.gd.uid
new file mode 100644
index 0000000..e3b0ff7
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/capture_mouse.gd.uid
@@ -0,0 +1 @@
+uid://dqdyrkm3jily6
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_loader.gd b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_loader.gd
new file mode 100644
index 0000000..501be98
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_loader.gd
@@ -0,0 +1,45 @@
+@tool
+class_name LevelLoader
+extends Node
+## Loads scenes into a container.
+
+signal level_load_started
+signal level_loaded
+signal level_ready
+
+## Container where the level instance will be added.
+@export var level_container : Node
+## Optional reference to a loading screen in the scene.
+@export var level_loading_screen : LoadingScreen
+@export_group("Debugging")
+@export var current_level : Node
+
+var current_level_path : String
+var is_loading : bool = false
+
+func _attach_level(level_resource : Resource):
+ assert(level_container != null, "level_container is null")
+ var instance = level_resource.instantiate()
+ level_container.call_deferred("add_child", instance)
+ return instance
+
+func load_level(level_path : String):
+ if is_loading : return
+ if is_instance_valid(current_level):
+ current_level.queue_free()
+ await current_level.tree_exited
+ current_level = null
+ current_level_path = level_path
+ is_loading = true
+ SceneLoader.load_scene(current_level_path, true)
+ if level_loading_screen:
+ level_loading_screen.reset()
+ level_load_started.emit()
+ await SceneLoader.scene_loaded
+ is_loading = false
+ current_level = _attach_level(SceneLoader.get_resource())
+ if level_loading_screen:
+ level_loading_screen.close()
+ level_loaded.emit()
+ await current_level.ready
+ level_ready.emit()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_loader.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_loader.gd.uid
new file mode 100644
index 0000000..d3c169c
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_loader.gd.uid
@@ -0,0 +1 @@
+uid://bbymrin0cm704
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_manager.gd b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_manager.gd
new file mode 100644
index 0000000..d74819b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_manager.gd
@@ -0,0 +1,180 @@
+class_name LevelManager
+extends Node
+## Manage level changes in games.
+##
+## A helper script to assign to a node in a scene.
+## It works with a level loader and can open menus when players win or lose.
+## It can either be assigned a starting level path or a scene lister.
+## It can detect signals from levels to change levels in an open-world.
+## With a scene lister, it will instead traverse through levels linearly.
+
+## Required reference to a level loader in the scene.
+@export var level_loader : LevelLoader
+## Optional path to a starting level scene.
+## Required if there is no scene lister.
+@export_file var starting_level_path : String
+## Optional reference to a scene lister in the scene.
+## Required if there is no starting level path.
+@export var scene_lister : SceneLister
+## Whether to load the starting level when ready.
+@export var auto_load : bool = true
+@export_group("Scenes")
+## Path to a main menu scene.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var main_menu_scene_path : String
+## Optional path to an ending scene.
+## Will attempt to read from AppConfig if left empty
+@export_file("*.tscn") var ending_scene_path : String
+## Optional screen to be shown after the game is won.
+@export var game_won_scene : PackedScene
+## Optional screen to be shown after the level is lost.
+@export var level_lost_scene : PackedScene
+## Optional screen to be shown after the level is won.
+@export var level_won_scene : PackedScene
+
+## Reference to the current level node.
+var current_level : Node
+var current_level_path : String : set = set_current_level_path
+var checkpoint_level_path : String : set = set_checkpoint_level_path
+
+func set_current_level_path(value : String) -> void:
+ current_level_path = value
+
+func set_checkpoint_level_path(value : String) -> void:
+ checkpoint_level_path = value
+
+func _try_connecting_signal_to_node(node : Node, signal_name : String, callable : Callable) -> void:
+ if node.has_signal(signal_name) and not node.is_connected(signal_name, callable):
+ node.connect(signal_name, callable)
+
+func _try_connecting_signal_to_level(signal_name : String, callable : Callable) -> void:
+ _try_connecting_signal_to_node(current_level, signal_name, callable)
+
+func get_main_menu_scene_path() -> String:
+ if main_menu_scene_path.is_empty():
+ return AppConfig.main_menu_scene_path
+ return main_menu_scene_path
+
+func _load_main_menu() -> void:
+ SceneLoader.load_scene(get_main_menu_scene_path())
+
+func _find_in_scene_lister(level_path : String) -> int:
+ if not scene_lister: return -1
+ level_path = ResourceUID.ensure_path(level_path)
+ return scene_lister.files.find(level_path)
+
+func is_on_last_level() -> bool:
+ var current_level_id = _find_in_scene_lister(current_level_path)
+ return current_level_id > -1 and current_level_id == scene_lister.files.size() - 1
+
+func get_relative_level_path(offset : int = 1) -> String:
+ var current_level_id := _find_in_scene_lister(current_level_path)
+ if current_level_id > -1:
+ if current_level_id >= max(0, -(offset)) and current_level_id < scene_lister.files.size() - max(0, offset):
+ current_level_id += offset
+ return scene_lister.files[current_level_id]
+ return ""
+
+func get_next_level_path() -> String:
+ return get_relative_level_path(1)
+
+func get_prev_level_path() -> String:
+ return get_relative_level_path(-1)
+
+func get_ending_scene_path() -> String:
+ if ending_scene_path.is_empty():
+ return AppConfig.ending_scene_path
+ return ending_scene_path
+
+func _load_ending() -> void:
+ if not get_ending_scene_path().is_empty():
+ SceneLoader.load_scene(get_ending_scene_path())
+ else:
+ _load_main_menu()
+
+func _on_level_lost() -> void:
+ if level_lost_scene:
+ var instance = level_lost_scene.instantiate()
+ get_tree().current_scene.add_child(instance)
+ _try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
+ _try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
+ else:
+ _reload_level()
+
+func get_checkpoint_level_path() -> String:
+ if checkpoint_level_path.is_empty():
+ if scene_lister:
+ return scene_lister.files.front()
+ if not starting_level_path.is_empty():
+ return starting_level_path
+ return checkpoint_level_path
+
+func load_level(level_path : String) -> void:
+ current_level_path = level_path
+ level_loader.load_level(level_path)
+
+func _load_checkpoint_level() -> void:
+ load_level(get_checkpoint_level_path())
+
+func _reload_level() -> void:
+ load_level(current_level_path)
+
+func _load_win_screen_or_ending() -> void:
+ if game_won_scene:
+ var instance = game_won_scene.instantiate()
+ get_tree().current_scene.add_child(instance)
+ _try_connecting_signal_to_node(instance, &"continue_pressed", _load_ending)
+ _try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
+ _try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
+ else:
+ _load_ending()
+
+func _load_level_won_screen_or_checkpoint() -> void:
+ if level_won_scene:
+ var instance = level_won_scene.instantiate()
+ get_tree().current_scene.add_child(instance)
+ _try_connecting_signal_to_node(instance, &"continue_pressed", _load_checkpoint_level)
+ _try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
+ _try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
+ else:
+ _load_checkpoint_level()
+
+func _on_level_won(next_level_path : String = ""):
+ if next_level_path.is_empty():
+ next_level_path = get_next_level_path()
+ if next_level_path.is_empty():
+ _load_win_screen_or_ending()
+ else:
+ checkpoint_level_path = next_level_path
+ _load_level_won_screen_or_checkpoint()
+
+func _on_level_changed(next_level_path : String):
+ checkpoint_level_path = next_level_path
+ _load_checkpoint_level()
+
+func _connect_level_signals() -> void:
+ _try_connecting_signal_to_level(&"level_lost", _on_level_lost)
+ _try_connecting_signal_to_level(&"level_won", _on_level_won)
+ _try_connecting_signal_to_level(&"level_changed", _on_level_changed)
+
+func _on_level_loader_level_loaded() -> void:
+ current_level = level_loader.current_level
+ await current_level.ready
+ _connect_level_signals()
+
+func _on_level_loader_level_load_started() -> void:
+ pass
+
+func _on_level_loader_level_ready() -> void:
+ pass
+
+func _auto_load() -> void:
+ if auto_load:
+ _load_checkpoint_level()
+
+func _ready() -> void:
+ if Engine.is_editor_hint(): return
+ level_loader.level_loaded.connect(_on_level_loader_level_loaded)
+ level_loader.level_ready.connect(_on_level_loader_level_ready)
+ level_loader.level_load_started.connect(_on_level_loader_level_load_started)
+ _auto_load()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_manager.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_manager.gd.uid
new file mode 100644
index 0000000..67c0b4c
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/level_manager.gd.uid
@@ -0,0 +1 @@
+uid://bllg4gg7v1tsr
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/scene_lister.gd b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/scene_lister.gd
new file mode 100644
index 0000000..c2bb4c4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/scene_lister.gd
@@ -0,0 +1,23 @@
+@tool
+extends Node
+class_name SceneLister
+## Helper class for listing all the scenes in a directory.
+
+## List of paths to scene files.
+## Prefilled in the editor by selecting a directory.
+@export var files : Array[String]
+## Prefill files with any scenes in the directory.
+@export_dir var directory : String :
+ set(value):
+ directory = value
+ _refresh_files()
+
+func _refresh_files():
+ if not is_inside_tree() or directory.is_empty(): return
+ var dir_access = DirAccess.open(directory)
+ if dir_access:
+ files.clear()
+ for file in dir_access.get_files():
+ if not file.ends_with(".tscn"):
+ continue
+ files.append(directory + "/" + file)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/scene_lister.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/scene_lister.gd.uid
new file mode 100644
index 0000000..ae7cd7f
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/scene_lister.gd.uid
@@ -0,0 +1 @@
+uid://wjq7li836lwj
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/win_lose_manager.gd b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/win_lose_manager.gd
new file mode 100644
index 0000000..f221609
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/win_lose_manager.gd
@@ -0,0 +1,70 @@
+extends Node
+
+## Path to a main menu scene.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var main_menu_scene_path : String
+## Optional path to an ending scene.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var ending_scene_path : String
+## Optional screen to be shown after the game is won.
+@export var game_won_scene : PackedScene
+## Optional screen to be shown after the game is lost.
+@export var game_lost_scene : PackedScene
+
+var has_lost_game : bool = false
+var has_won_game : bool = false
+
+func _try_connecting_signal_to_node(node : Node, signal_name : String, callable : Callable) -> void:
+ if node.has_signal(signal_name) and not node.is_connected(signal_name, callable):
+ node.connect(signal_name, callable)
+
+func get_main_menu_scene_path() -> String:
+ if main_menu_scene_path.is_empty():
+ return AppConfig.main_menu_scene_path
+ return main_menu_scene_path
+
+func _load_main_menu() -> void:
+ SceneLoader.load_scene(get_main_menu_scene_path())
+
+func get_ending_scene_path() -> String:
+ if ending_scene_path.is_empty():
+ return AppConfig.ending_scene_path
+ return ending_scene_path
+
+func _load_ending() -> void:
+ if get_ending_scene_path().is_empty():
+ _load_main_menu()
+ else:
+ SceneLoader.load_scene(get_ending_scene_path())
+
+func _load_lose_screen_or_reload() -> void:
+ if game_lost_scene:
+ var instance = game_lost_scene.instantiate()
+ get_tree().current_scene.add_child(instance)
+ _try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
+ _try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
+ else:
+ _reload_level()
+
+func _reload_level() -> void:
+ SceneLoader.reload_current_scene()
+
+func _load_win_screen_or_ending() -> void:
+ if game_won_scene:
+ var instance = game_won_scene.instantiate()
+ get_tree().current_scene.add_child(instance)
+ _try_connecting_signal_to_node(instance, &"continue_pressed", _load_ending)
+ _try_connecting_signal_to_node(instance, &"restart_pressed", _reload_level)
+ _try_connecting_signal_to_node(instance, &"main_menu_pressed", _load_main_menu)
+ else:
+ _load_ending()
+
+func game_lost() -> void:
+ if has_won_game or has_lost_game: return
+ has_lost_game = true
+ _load_lose_screen_or_reload()
+
+func game_won() -> void:
+ if has_won_game or has_lost_game: return
+ has_won_game = true
+ _load_win_screen_or_ending()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/win_lose_manager.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/win_lose_manager.gd.uid
new file mode 100644
index 0000000..32aa4d2
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/extras/scripts/win_lose_manager.gd.uid
@@ -0,0 +1 @@
+uid://bmlwpkrav3q56
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.gd
new file mode 100644
index 0000000..a867401
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.gd
@@ -0,0 +1,86 @@
+@tool
+extends Node
+## Script for comparing the version of a plugin to the latest release on GitHub.
+
+signal new_version_detected(version: String)
+signal versions_matched
+signal failed
+
+const APIClient = MaaacksGameTemplatePlugin.APIClient
+
+const API_RELEASES_URL := "https://api.github.com/repos/%s/%s/releases"
+
+## The directory of the plugin to update. Typically in res://addons/.
+@export var plugin_directory : String
+## The URL of the GitHub repo to pull new releases.
+@export var plugin_github_url : String :
+ set(value):
+ plugin_github_url = value
+ _update_urls()
+@export_group("Advanced")
+## If true, automatically check for a new version when ready.
+@export var auto_start : bool = false
+## Text to remove from the tag before comparing versions.
+@export var replace_tag_name : String = "v"
+## The default lowest version to display.
+@export var default_version : String = "0.0.0"
+## If true, test comparing versions.
+## Replace with @export_tool_button for Godot 4.4+
+@export var _test_action : bool = false :
+ set(value):
+ if value and Engine.is_editor_hint():
+ compare_versions()
+
+@onready var _api_client : APIClient = $APIClient
+
+var _zipball_url : String
+
+func get_plugin_version() -> String :
+ if not plugin_directory.is_empty():
+ for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
+ if enabled_plugin.contains(plugin_directory):
+ var config := ConfigFile.new()
+ var error = config.load(enabled_plugin)
+ if error != OK:
+ return default_version
+ return config.get_value("plugin", "version", default_version)
+ return default_version
+
+func _update_urls() -> void:
+ if plugin_github_url.is_empty(): return
+ if _api_client == null: return
+ var regex := RegEx.create_from_string("https:\\/\\/github\\.com\\/([\\w-]+)\\/([\\w-]+)\\/*")
+ var regex_match := regex.search(plugin_github_url)
+ if regex_match == null: return
+ var username := regex_match.get_string(1)
+ var repository := regex_match.get_string(2)
+ _api_client.api_url = API_RELEASES_URL % [username, repository]
+
+func _on_api_client_request_failed(error) -> void:
+ failed.emit()
+ queue_free()
+
+func _on_api_client_response_received(response_body) -> void:
+ if response_body is not Array or response_body.is_empty():
+ failed.emit()
+ queue_free()
+ return
+ var latest_release : Dictionary = response_body.front()
+ var tag_name := default_version
+ if latest_release.has("tag_name"):
+ tag_name = latest_release["tag_name"]
+ if replace_tag_name:
+ tag_name = tag_name.replacen(replace_tag_name, "")
+ var current_tag_name = get_plugin_version()
+ if tag_name != current_tag_name:
+ new_version_detected.emit(tag_name)
+ else:
+ versions_matched.emit()
+ queue_free()
+
+func compare_versions() -> void:
+ _api_client.request()
+
+func _ready() -> void:
+ if auto_start:
+ compare_versions()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.gd.uid
new file mode 100644
index 0000000..6211f05
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.gd.uid
@@ -0,0 +1 @@
+uid://ye1geusqp1gd
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.tscn
new file mode 100644
index 0000000..b0fed7c
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/check_plugin_version.tscn
@@ -0,0 +1,16 @@
+[gd_scene format=3 uid="uid://b5m61gpvjy1ao"]
+
+[ext_resource type="Script" uid="uid://ye1geusqp1gd" path="res://addons/maaacks_game_template/installer/check_plugin_version.gd" id="1_aqelj"]
+[ext_resource type="PackedScene" uid="uid://drhhakm62vjsy" path="res://addons/maaacks_game_template/utilities/api_client.tscn" id="2_5myc0"]
+
+[node name="CheckPluginVersion" type="Node" unique_id=976061680]
+script = ExtResource("1_aqelj")
+plugin_directory = "maaacks_game_template"
+plugin_github_url = "https://github.com/Maaack/Godot-Game-Template"
+
+[node name="APIClient" parent="." unique_id=716495464 instance=ExtResource("2_5myc0")]
+api_url = "https://api.github.com/repos/Maaack/Godot-Game-Template/releases"
+request_method = 0
+
+[connection signal="request_failed" from="APIClient" to="." method="_on_api_client_request_failed"]
+[connection signal="response_received" from="APIClient" to="." method="_on_api_client_response_received"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.gd
new file mode 100644
index 0000000..2842e79
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.gd
@@ -0,0 +1,176 @@
+@tool
+extends Node
+## Script for automatically copying Godot scenes and scripts without UIDs.
+
+signal canceled
+signal completed(target_path : String)
+
+const UID_PREG_MATCH = r'uid="uid:\/\/[0-9a-z]+" '
+const RUNNING_CHECK_DELAY : float = 0.25
+const RESAVING_DELAY : float = 1.0
+const RAW_COPY_EXTENSIONS : Array = ["gd", "md", "txt"]
+const OMIT_COPY_EXTENSIONS : Array = ["uid"]
+const REPLACE_CONTENT_EXTENSIONS : Array = ["gd", "tscn", "tres", "md"]
+
+@onready var destination_dialog : FileDialog = $DestinationDialog
+
+@export_dir var relative_path : String :
+ set(value):
+ relative_path = value
+ if not relative_path.ends_with("/"):
+ relative_path += "/"
+@export var replace_strings_map : Dictionary
+@export var visible : bool = true :
+ set(value):
+ visible = value
+ if is_inside_tree():
+ destination_dialog.visible = visible
+
+func show() -> void:
+ visible = true
+
+func hide() -> void:
+ visible = false
+
+func close() -> void:
+ queue_free()
+
+func _remove_uids(content : String) -> String:
+ var regex = RegEx.new()
+ regex.compile(UID_PREG_MATCH)
+ return regex.sub(content, "", true)
+
+func _replace_paths(content : String, target_path : String) -> String:
+ return content.replace(relative_path.trim_prefix("res://"), target_path.trim_prefix("res://"))
+
+func _replace_strings(content : String) -> String:
+ for key in replace_strings_map:
+ var value : String = replace_strings_map[key]
+ content = content.replace(key, value)
+ return content
+
+func _replace_content(content : String, target_path : String) -> String:
+ var replaced_content : String
+ replaced_content = _remove_uids(content)
+ replaced_content = _replace_paths(replaced_content, target_path)
+ replaced_content = _replace_strings(replaced_content)
+ return replaced_content
+
+func _replace_file_contents(file_path : String, target_path : String) -> void:
+ var extension : String = file_path.get_extension()
+ if extension not in REPLACE_CONTENT_EXTENSIONS:
+ return
+ var file = FileAccess.open(file_path, FileAccess.READ)
+ if file == null:
+ push_error("plugin error - null file: `%s`" % file_path)
+ return
+ var original_content = file.get_as_text()
+ file.close()
+ var replaced_content := _replace_content(original_content, target_path)
+ if replaced_content == original_content: return
+ file = FileAccess.open(file_path, FileAccess.WRITE)
+ file.store_string(replaced_content)
+ file.close()
+
+func _save_resource(resource_path : String, resource_destination : String, whitelisted_extensions : PackedStringArray = []) -> Error:
+ var extension : String = resource_path.get_extension()
+ if whitelisted_extensions.size() > 0:
+ if not extension in whitelisted_extensions:
+ return OK
+ if extension == "import":
+ # skip import files
+ return OK
+ var file_object = load(resource_path)
+ if file_object is Resource:
+ var possible_extensions = ResourceSaver.get_recognized_extensions(file_object)
+ if possible_extensions.has(extension):
+ return ResourceSaver.save(file_object, resource_destination, ResourceSaver.FLAG_CHANGE_PATH)
+ else:
+ return ERR_FILE_UNRECOGNIZED
+ else:
+ return ERR_FILE_UNRECOGNIZED
+ return OK
+
+func _raw_copy_file_path(file_path : String, destination_path : String) -> Error:
+ var dir := DirAccess.open("res://")
+ var error := dir.copy(file_path, destination_path)
+ return error
+
+func _copy_file_path(file_path : String, destination_path : String, target_path : String) -> Error:
+ var error : Error
+ if file_path.get_extension() in OMIT_COPY_EXTENSIONS:
+ return error
+ if file_path.get_extension() in RAW_COPY_EXTENSIONS:
+ error = _raw_copy_file_path(file_path, destination_path)
+ else:
+ error = _save_resource(file_path, destination_path)
+ if error == ERR_FILE_UNRECOGNIZED:
+ error = _raw_copy_file_path(file_path, destination_path)
+ if not error:
+ _replace_file_contents(destination_path, target_path)
+ return error
+
+func _copy_directory_path(dir_path : String, target_path : String) -> void:
+ if not dir_path.ends_with("/"):
+ dir_path += "/"
+ var dir = DirAccess.open(dir_path)
+ if dir:
+ dir.list_dir_begin()
+ var file_name = dir.get_next()
+ var error : Error
+ while file_name != "" and error == 0:
+ var file_relative_path = dir_path.trim_prefix(relative_path)
+ var destination_path = target_path + file_relative_path + file_name
+ var full_file_path = dir_path + file_name
+ if dir.current_is_dir():
+ if not dir.dir_exists(destination_path):
+ error = dir.make_dir(destination_path)
+ _copy_directory_path(full_file_path, target_path)
+ else:
+ error = _copy_file_path(full_file_path, destination_path, target_path)
+ file_name = dir.get_next()
+ if error:
+ push_error("plugin error - copying path: %s" % error)
+ else:
+ push_error("plugin error - accessing path: %s" % dir_path)
+
+func _complete(target_path : String) -> void:
+ completed.emit(target_path)
+ close()
+
+func _wait_for_scan_and_complete(target_path : String) -> void:
+ var timer: Timer = Timer.new()
+ var callable := func():
+ if EditorInterface.get_resource_filesystem().is_scanning(): return
+ timer.stop()
+ _complete(target_path)
+ timer.queue_free()
+ timer.timeout.connect(callable)
+ add_child(timer)
+ timer.start(RUNNING_CHECK_DELAY)
+
+func _delayed_saving_and_next_prompt(target_path : String) -> void:
+ var timer: Timer = Timer.new()
+ var callable := func():
+ timer.stop()
+ EditorInterface.save_all_scenes()
+ EditorInterface.get_resource_filesystem().scan_sources()
+ EditorInterface.get_resource_filesystem().scan()
+ _wait_for_scan_and_complete(target_path)
+ timer.queue_free()
+ timer.timeout.connect(callable)
+ add_child(timer)
+ timer.start(RESAVING_DELAY)
+
+func _copy_to_directory(target_path : String) -> void:
+ if not target_path.ends_with("/"):
+ target_path += "/"
+ _copy_directory_path(relative_path, target_path)
+ _delayed_saving_and_next_prompt(target_path)
+
+func _on_destination_dialog_dir_selected(dir):
+ _copy_to_directory(dir)
+
+func _on_destination_dialog_canceled():
+ canceled.emit()
+ close()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.gd.uid
new file mode 100644
index 0000000..4aa510b
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.gd.uid
@@ -0,0 +1 @@
+uid://bgdxso8wdsm2l
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.tscn
new file mode 100644
index 0000000..e93dd9a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_and_edit_files.tscn
@@ -0,0 +1,16 @@
+[gd_scene format=3 uid="uid://bcgr0b6wp5b5d"]
+
+[ext_resource type="Script" uid="uid://bgdxso8wdsm2l" path="res://addons/maaacks_game_template/installer/copy_and_edit_files.gd" id="1_oyky1"]
+[ext_resource type="PackedScene" uid="uid://ckx50am7thhd2" path="res://addons/maaacks_game_template/installer/destination_dialog.tscn" id="2_g35hh"]
+
+[node name="CopyAndEditFiles" type="Node" unique_id=1583348979]
+script = ExtResource("1_oyky1")
+relative_path = "res://addons/maaacks_game_template/examples/"
+replace_strings_map = {
+"StateExample": "State"
+}
+
+[node name="DestinationDialog" parent="." unique_id=1967756482 instance=ExtResource("2_g35hh")]
+
+[connection signal="canceled" from="DestinationDialog" to="." method="_on_destination_dialog_canceled"]
+[connection signal="dir_selected" from="DestinationDialog" to="." method="_on_destination_dialog_dir_selected"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/copy_confirmation_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_confirmation_dialog.tscn
new file mode 100644
index 0000000..705def2
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/copy_confirmation_dialog.tscn
@@ -0,0 +1,15 @@
+[gd_scene format=3 uid="uid://cyx4i4v30bw4o"]
+
+[node name="CopyConfirmationDialog" type="ConfirmationDialog" unique_id=540119939]
+oversampling_override = 1.0
+title = "Copy Examples"
+initial_position = 2
+size = Vector2i(1024, 148)
+visible = true
+exclusive = false
+ok_button_text = "Yes"
+dialog_text = "Plugin enabled. It is recommended to copy the example scenes to a destination outside of the addons/ folder before editing them.
+
+Would you like to copy the examples now?"
+dialog_autowrap = true
+cancel_button_text = "No"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/delete_examples_confirmation_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/delete_examples_confirmation_dialog.tscn
new file mode 100644
index 0000000..c3a3ffb
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/delete_examples_confirmation_dialog.tscn
@@ -0,0 +1,16 @@
+[gd_scene format=3 uid="uid://vgdxevcnv0vx"]
+
+[node name="DeleteExamplesConfirmationDialog" type="ConfirmationDialog" unique_id=1467285077]
+oversampling_override = 1.0
+title = "Delete Source Examples"
+initial_position = 2
+size = Vector2i(1024, 256)
+visible = true
+ok_button_text = "Yes"
+dialog_text = "If the copied scenes work as expected, you may delete the source examples folder. This avoids confusing both developers and the Godot editor.
+
+This will also remove the option to copy the examples again. However, one copy is enough for most use cases.
+
+Would you like to delete the source examples folder now?"
+dialog_autowrap = true
+cancel_button_text = "No"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/delete_examples_short_confirmation_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/delete_examples_short_confirmation_dialog.tscn
new file mode 100644
index 0000000..535b5cd
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/delete_examples_short_confirmation_dialog.tscn
@@ -0,0 +1,17 @@
+[gd_scene format=3 uid="uid://d03csqgcaxm0m"]
+
+[node name="DeleteExamplesShortConfirmationDialog" type="ConfirmationDialog" unique_id=1084442550]
+oversampling_override = 1.0
+title = "Delete Source Examples"
+initial_position = 2
+size = Vector2i(1024, 300)
+visible = true
+ok_button_text = "Yes"
+dialog_text = "This will delete the original examples folder from the plugin (inside of addons/).
+
+Copies of the examples (outside of addons/) will not be affected.
+
+Are you sure you would like to delete the examples folder?
+"
+dialog_autowrap = true
+cancel_button_text = "No"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/destination_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/destination_dialog.tscn
new file mode 100644
index 0000000..8c532d0
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/destination_dialog.tscn
@@ -0,0 +1,12 @@
+[gd_scene format=3 uid="uid://ckx50am7thhd2"]
+
+[node name="DestinationDialog" type="FileDialog" unique_id=475642814]
+oversampling_override = 1.0
+title = "Select a Destination"
+initial_position = 2
+size = Vector2i(1024, 640)
+visible = true
+exclusive = false
+ok_button_text = "Select Current Folder"
+mode_overrides_title = false
+file_mode = 2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd
new file mode 100644
index 0000000..4d79818
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd
@@ -0,0 +1,12 @@
+@tool
+extends ConfirmationDialog
+
+const SHORT_DESCRIPTION : String = "Choose a style for icons in the input remapping menu. This style can be changed later."
+
+signal configuration_selected(index : int)
+
+func _on_item_list_item_selected(index) -> void:
+ configuration_selected.emit(index)
+
+func set_short_description() -> void:
+ %Label.text = SHORT_DESCRIPTION
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd.uid
new file mode 100644
index 0000000..ec63fee
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd.uid
@@ -0,0 +1 @@
+uid://bduy6qihnm0qo
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.tscn
new file mode 100644
index 0000000..a4d794e
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_dialog.tscn
@@ -0,0 +1,84 @@
+[gd_scene format=3 uid="uid://d3x8m40qtdrj"]
+
+[ext_resource type="Script" uid="uid://bduy6qihnm0qo" path="res://addons/maaacks_game_template/installer/kenney_input_prompts_dialog.gd" id="1_nf1bc"]
+[ext_resource type="Texture2D" uid="uid://cmni5hv40bfaa" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored.png" id="2_0nqam"]
+[ext_resource type="Texture2D" uid="uid://deskx061vlcgx" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white.png" id="3_ynuxh"]
+[ext_resource type="Texture2D" uid="uid://bohem6w6kcl3x" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored.png" id="4_dqbfh"]
+[ext_resource type="Texture2D" uid="uid://bq211jkfnm7k7" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white.png" id="5_1tkva"]
+[ext_resource type="Texture2D" uid="uid://bt1yqttw3d5xn" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored-2x.png" id="6_r3yyh"]
+[ext_resource type="Texture2D" uid="uid://bit8o3p506th6" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white-2x.png" id="7_xgp8o"]
+[ext_resource type="Texture2D" uid="uid://cqb86gp1gh3y8" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-2x.png" id="8_ag5dy"]
+[ext_resource type="Texture2D" uid="uid://d3bsc6o2ae88q" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white-2x.png" id="9_3b8mx"]
+[ext_resource type="Texture2D" uid="uid://ix1d2e62f233" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-colored-vector.svg" id="10_ag5dy"]
+[ext_resource type="Texture2D" uid="uid://c37gofthe2bh3" path="res://addons/maaacks_game_template/assets/input-icons/icons-filled-white-vector.svg" id="11_3b8mx"]
+[ext_resource type="Texture2D" uid="uid://bsgf78aysgdnd" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-colored-vector.svg" id="12_rrkvx"]
+[ext_resource type="Texture2D" uid="uid://c1lpc33fpmd4p" path="res://addons/maaacks_game_template/assets/input-icons/icons-outlined-white-vector.svg" id="13_bkfjd"]
+
+[node name="KenneyInputPromptsDialog" type="ConfirmationDialog" unique_id=141817893]
+oversampling_override = 1.0
+title = "Add Kenney Input Prompts Pack"
+initial_position = 2
+size = Vector2i(1024, 640)
+visible = true
+ok_button_text = "Yes"
+dialog_autowrap = true
+cancel_button_text = "No"
+script = ExtResource("1_nf1bc")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1531022853]
+custom_minimum_size = Vector2(560, 443)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 8.0
+offset_top = 8.0
+offset_right = -8.0
+offset_bottom = -49.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 16
+
+[node name="Label" type="Label" parent="VBoxContainer" unique_id=1200813079]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(384, 0)
+layout_mode = 2
+text = "Would you like to install Kenney's Input Prompts?
+
+This adds icons for a majority of input keys and devices in the input remapping menu. They are Creative Commons Zero (CC0) licensed, about 3.9 MB in size (7.6 MB with *.import files), and get installed into the assets folder.
+
+Choose a style for icons in the input remapping menu. The style can be changed later."
+autowrap_mode = 3
+
+[node name="ItemList" type="ItemList" parent="VBoxContainer" unique_id=1072267315]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+item_count = 12
+same_column_width = true
+item_0/text = "Filled and Colored Vector"
+item_0/icon = ExtResource("10_ag5dy")
+item_1/text = "Filled and White Vector"
+item_1/icon = ExtResource("11_3b8mx")
+item_2/text = "Outlined and Colored Vector"
+item_2/icon = ExtResource("12_rrkvx")
+item_3/text = "Outlined and White Vector"
+item_3/icon = ExtResource("13_bkfjd")
+item_4/text = "Filled and Colored 64x64"
+item_4/icon = ExtResource("2_0nqam")
+item_5/text = "Filled and White 64x64"
+item_5/icon = ExtResource("3_ynuxh")
+item_6/text = "Outlined and Colored 64x64"
+item_6/icon = ExtResource("4_dqbfh")
+item_7/text = "Outlined and White 64x64"
+item_7/icon = ExtResource("5_1tkva")
+item_8/text = "Filled and Colored 128x128"
+item_8/icon = ExtResource("6_r3yyh")
+item_9/text = "Filled and White 128x128"
+item_9/icon = ExtResource("7_xgp8o")
+item_10/text = "Outlined and Colored 128x128"
+item_10/icon = ExtResource("8_ag5dy")
+item_11/text = "Outlined and White 128x128"
+item_11/icon = ExtResource("9_3b8mx")
+
+[connection signal="item_selected" from="VBoxContainer/ItemList" to="." method="_on_item_list_item_selected"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd
new file mode 100644
index 0000000..d62d16d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd
@@ -0,0 +1,336 @@
+@tool
+## Tool for installing icons and setting up the configuration of the input icon mapper.
+extends Node
+
+## Sent when the user selects to cancel the installation process.
+signal canceled
+## Sent when the installation process has completed.
+signal completed
+
+const DownloadAndExtract = MaaacksGameTemplatePlugin.DownloadAndExtract
+const RELATIVE_PATH_TO_CONFIGURE_SCENE = "scenes/menus/options_menu/input/input_icon_mapper.tscn"
+const REIMPORT_CHECK_DELAY : float = 0.5
+const OPEN_SCENE_DELAY : float = 0.5
+const MATCH_REGEX = """(\\[node name="InputIconMapper" (unique_id=[0-9]+ )?instance=ExtResource\\("[0-9a-z_]+"\\)\\])[\\s\\S]*"""
+
+const FILLED_WHITE_CONFIGURATION = """
+replace_strings = {
+"Capslock": "Caps Lock",
+"Generic Stick": "Generic Left Stick",
+"Guide": "Home",
+"Slash Back": "Back Slash",
+"Slash Forward": "Slash",
+"Stick L": "Left Stick",
+"Stick R": "Right Stick",
+"Trigger L 1": "Left Shoulder",
+"Trigger L 2": "Left Trigger",
+"Trigger R 1": "Right Shoulder",
+"Trigger R 2": "Right Trigger"
+}
+filtered_strings = Array[String](["keyboard", "color", "button", "arrow"])
+directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
+filter = "color"
+ends_with = ".png"
+not_ends_with = "outline.png"
+"""
+const FILLED_COLOR_CONFIGURATION = """
+prioritized_strings = Array[String](["color"])
+replace_strings = {
+"Capslock": "Caps Lock",
+"Generic Stick": "Generic Left Stick",
+"Guide": "Home",
+"Slash Back": "Back Slash",
+"Slash Forward": "Slash",
+"Stick L": "Left Stick",
+"Stick R": "Right Stick",
+"Trigger L 1": "Left Shoulder",
+"Trigger L 2": "Left Trigger",
+"Trigger R 1": "Right Shoulder",
+"Trigger R 2": "Right Trigger"
+}
+filtered_strings = Array[String](["keyboard", "color", "button", "arrow"])
+directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
+ends_with = ".png"
+not_ends_with = "outline.png"
+"""
+const OUTLINED_WHITE_CONFIGURATION = """
+prioritized_strings = Array[String](["outline"])
+replace_strings = {
+"Capslock": "Caps Lock",
+"Generic Stick": "Generic Left Stick",
+"Guide": "Home",
+"Slash Back": "Back Slash",
+"Slash Forward": "Slash",
+"Stick L": "Left Stick",
+"Stick R": "Right Stick",
+"Trigger L 1": "Left Shoulder",
+"Trigger L 2": "Left Trigger",
+"Trigger R 1": "Right Shoulder",
+"Trigger R 2": "Right Trigger"
+}
+filtered_strings = Array[String](["keyboard", "color", "button", "arrow", "outline"])
+directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
+filter = "color"
+ends_with = ".png"
+"""
+const OUTLINED_COLOR_CONFIGURATION = """
+prioritized_strings = Array[String](["outline", "color"])
+replace_strings = {
+"Capslock": "Caps Lock",
+"Generic Stick": "Generic Left Stick",
+"Guide": "Home",
+"Slash Back": "Back Slash",
+"Slash Forward": "Slash",
+"Stick L": "Left Stick",
+"Stick R": "Right Stick",
+"Trigger L 1": "Left Shoulder",
+"Trigger L 2": "Left Trigger",
+"Trigger R 1": "Right Shoulder",
+"Trigger R 2": "Right Trigger"
+}
+filtered_strings = Array[String](["keyboard", "color", "button", "arrow", "outline"])
+directories = Array[String](["res://assets/kenney_input-prompts/Keyboard & Mouse/Default", "res://assets/kenney_input-prompts/Generic/Default", "res://assets/kenney_input-prompts/Xbox Series/Default", "res://assets/kenney_input-prompts/PlayStation Series/Default", "res://assets/kenney_input-prompts/Nintendo Switch/Default", "res://assets/kenney_input-prompts/Steam Deck/Default"])
+ends_with = ".png"
+"""
+
+const PACKAGE_EXTRA_DIRECTORIES := [
+ "Flairs",
+ "Nintendo Gamecube",
+ "Nintendo Switch 2",
+ "Nintendo Wii",
+ "Nintendo WiiU",
+ "Playdate",
+ "Steam Controller",
+ "Touch",
+]
+
+const PACKAGE_EXTRA_FILES := [
+ "Preview",
+]
+
+## Path start where the project examples have been copied.
+@export_dir var copy_dir_path : String
+## Path end where the zipped files are to be extracted.
+@export var extract_extension : String
+
+@onready var _download_and_extract_node : DownloadAndExtract = $DownloadAndExtract
+@onready var _skip_installation_dialog : ConfirmationDialog = $SkipInstallationDialog
+@onready var _kenney_input_prompts_dialog : ConfirmationDialog = $KenneyInputPromptsDialog
+@onready var _installing_dialog : AcceptDialog = $InstallingDialog
+@onready var _clean_up_dialog : ConfirmationDialog = $CleanUpDialog
+@onready var _error_dialog : AcceptDialog = $ErrorDialog
+@onready var _stage_label : Label = %StageLabel
+@onready var _progress_bar : ProgressBar = %ProgressBar
+
+var _configuration_index : int = -1
+## State flag of whether the tool is waiting for the filesystem to finish scanning.
+var scanning : bool = false
+## State flag for whether the tool is waiting for the filesystem to finish reimporting.
+var reimporting : bool = false
+## Flag for whether the tool will force a download and extraction, even if the contents exist.
+var force : bool = false
+
+func _download_and_extract() -> void:
+ _installing_dialog.show()
+ _download_and_extract_node.run.call_deferred()
+
+func _run_complete() -> void:
+ completed.emit()
+ queue_free()
+
+func _clean_up_or_complete() -> void:
+ if _has_extras():
+ _clean_up_dialog.show()
+ else:
+ _run_complete()
+
+func _process(_delta : float) -> void:
+ if _installing_dialog.visible:
+ _progress_bar.value = _download_and_extract_node.get_progress()
+ match _download_and_extract_node.stage:
+ DownloadAndExtract.DownloadAndExtractStage.DOWNLOAD:
+ _stage_label.text = "Downloading..."
+ DownloadAndExtract.DownloadAndExtractStage.SAVE:
+ _stage_label.text = "Saving..."
+ DownloadAndExtract.DownloadAndExtractStage.EXTRACT:
+ _stage_label.text = "Extracting..."
+ DownloadAndExtract.DownloadAndExtractStage.DELETE:
+ _stage_label.text = "Cleaning up..."
+ DownloadAndExtract.DownloadAndExtractStage.NONE:
+ _installing_dialog.hide()
+ elif scanning:
+ var file_system := EditorInterface.get_resource_filesystem()
+ if not file_system.is_scanning():
+ scanning = false
+ await get_tree().create_timer(REIMPORT_CHECK_DELAY).timeout
+ if reimporting:
+ await file_system.resources_reimported
+ reimporting = false
+ _configure_and_complete()
+
+func _delete_recursive(path : String) -> void:
+ if not path.ends_with("/"):
+ path += "/"
+ var dir_access := DirAccess.open(path)
+ if dir_access == null:
+ return
+ var directories := dir_access.get_directories()
+ for directory in directories:
+ _delete_recursive(path + directory)
+ DirAccess.remove_absolute(path + directory)
+ var files := dir_access.get_files()
+ for file in files:
+ DirAccess.remove_absolute(path + file)
+
+func get_full_path() -> String:
+ var full_path := copy_dir_path
+ if not full_path.ends_with("/"):
+ full_path += "/"
+ full_path += extract_extension
+ if not full_path.ends_with("/"):
+ full_path += "/"
+ return full_path
+
+func _has_extras() -> bool:
+ var full_path := get_full_path()
+ var directories := DirAccess.get_directories_at(full_path)
+ for directory in directories:
+ for key in PACKAGE_EXTRA_DIRECTORIES:
+ if directory.contains(key):
+ return true
+ var files := DirAccess.get_files_at(full_path)
+ for file in files:
+ for key in PACKAGE_EXTRA_FILES:
+ if file.contains(key):
+ return true
+ return false
+
+func _delete_extras() -> void:
+ var full_path := get_full_path()
+ var directories := DirAccess.get_directories_at(full_path)
+ for directory in directories:
+ for key in PACKAGE_EXTRA_DIRECTORIES:
+ if directory.contains(key):
+ _delete_recursive(full_path + directory)
+ DirAccess.remove_absolute(full_path + directory)
+ continue
+ var files := DirAccess.get_files_at(full_path)
+ for file in files:
+ for key in PACKAGE_EXTRA_FILES:
+ if file.contains(key):
+ DirAccess.remove_absolute(full_path + file)
+ continue
+ EditorInterface.get_resource_filesystem().scan()
+
+func _configure_icons() -> void:
+ var input_mapper_path := copy_dir_path + RELATIVE_PATH_TO_CONFIGURE_SCENE
+ var icon_mapper_string := FileAccess.get_file_as_string(input_mapper_path)
+ var replacing_string := "$1\n"
+ match(_configuration_index % 4):
+ 0:
+ replacing_string += FILLED_COLOR_CONFIGURATION
+ 1:
+ replacing_string += FILLED_WHITE_CONFIGURATION
+ 2:
+ replacing_string += OUTLINED_COLOR_CONFIGURATION
+ 3:
+ replacing_string += OUTLINED_WHITE_CONFIGURATION
+ match(_configuration_index / 4):
+ 0:
+ replacing_string = replacing_string.replace("Default", "Vector").replace(".png", ".svg")
+ 1:
+ pass
+ 2:
+ replacing_string = replacing_string.replace("Default", "Double")
+ var regex = RegEx.new()
+ regex.compile(MATCH_REGEX)
+ icon_mapper_string = regex.sub(icon_mapper_string, replacing_string)
+ var file_rewrite := FileAccess.open(input_mapper_path, FileAccess.WRITE)
+ file_rewrite.store_string(icon_mapper_string)
+ file_rewrite.close()
+ if input_mapper_path in EditorInterface.get_open_scenes():
+ EditorInterface.reload_scene_from_path(input_mapper_path)
+ else:
+ EditorInterface.open_scene_from_path(input_mapper_path)
+ await get_tree().create_timer(OPEN_SCENE_DELAY).timeout
+ EditorInterface.save_scene()
+ await get_tree().create_timer(REIMPORT_CHECK_DELAY).timeout
+ _clean_up_or_complete()
+
+func _configure_and_complete() -> void:
+ if _configuration_index >= 0:
+ _configure_icons()
+ return
+ _clean_up_or_complete()
+
+func _scan_filesystem_and_reimport() -> void:
+ var file_system := EditorInterface.get_resource_filesystem()
+ file_system.scan()
+ scanning = true
+ await file_system.resources_reimporting
+ reimporting = true
+
+func _enable_forced_install() -> void:
+ force = true
+ _download_and_extract_node.force = true
+ _kenney_input_prompts_dialog.show.call_deferred()
+
+func _enable_skipped_install() -> void:
+ _kenney_input_prompts_dialog.set_short_description()
+ _kenney_input_prompts_dialog.show.call_deferred()
+
+func _show_error_dialog(error : String) -> void:
+ _installing_dialog.hide()
+ _error_dialog.show()
+ _error_dialog.dialog_text = "%s!" % error
+
+func _ready() -> void:
+ _skip_installation_dialog.hide()
+ _kenney_input_prompts_dialog.hide()
+ _installing_dialog.hide()
+ _installing_dialog.get_ok_button().hide()
+ _clean_up_dialog.hide()
+ _error_dialog.hide()
+ _download_and_extract_node.extract_path = get_full_path()
+ if _download_and_extract_node.extract_path_exists():
+ _skip_installation_dialog.show()
+ else:
+ _kenney_input_prompts_dialog.show()
+
+func _on_kenney_input_prompts_dialog_canceled() -> void:
+ canceled.emit()
+ queue_free()
+
+func _on_kenney_input_prompts_dialog_configuration_selected(index: int) -> void:
+ _configuration_index = index
+
+func _on_kenney_input_prompts_dialog_confirmed() -> void:
+ if _download_and_extract_node.extract_path_exists() and not force:
+ _configure_and_complete()
+ return
+ _download_and_extract()
+
+func _on_skip_installation_dialog_canceled() -> void:
+ _enable_forced_install()
+
+func _on_skip_installation_dialog_confirmed() -> void:
+ _enable_skipped_install()
+
+func _on_error_dialog_confirmed() -> void:
+ queue_free()
+
+func _on_error_dialog_canceled() -> void:
+ queue_free()
+
+func _on_download_and_extract_run_completed() -> void:
+ _scan_filesystem_and_reimport()
+
+func _on_download_and_extract_run_failed(error : String) -> void:
+ _show_error_dialog(error)
+
+func _on_clean_up_dialog_confirmed() -> void:
+ _delete_extras()
+ _run_complete()
+
+func _on_clean_up_dialog_canceled() -> void:
+ _run_complete()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd.uid
new file mode 100644
index 0000000..c6fb9c6
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd.uid
@@ -0,0 +1 @@
+uid://ca36dy2vkk46q
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.tscn
new file mode 100644
index 0000000..a8f60d4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/kenney_input_prompts_installer.tscn
@@ -0,0 +1,92 @@
+[gd_scene format=3 uid="uid://d27twrcwmfxrs"]
+
+[ext_resource type="Script" uid="uid://ca36dy2vkk46q" path="res://addons/maaacks_game_template/installer/kenney_input_prompts_installer.gd" id="1_ebstj"]
+[ext_resource type="PackedScene" uid="uid://d3x8m40qtdrj" path="res://addons/maaacks_game_template/installer/kenney_input_prompts_dialog.tscn" id="1_pslk0"]
+[ext_resource type="PackedScene" uid="uid://dlkmofxhavh10" path="res://addons/maaacks_game_template/utilities/download_and_extract.tscn" id="3_ebstj"]
+
+[node name="KenneyInputPromptsInstaller" type="Node" unique_id=910863368]
+script = ExtResource("1_ebstj")
+copy_dir_path = "res://"
+extract_extension = "assets/kenney_input-prompts"
+
+[node name="DownloadAndExtract" parent="." unique_id=227176651 instance=ExtResource("3_ebstj")]
+zip_url = "https://github.com/Maaack/Kenney-Input-Prompts/archive/refs/tags/1.3.zip"
+extract_path = "res://assets/kenney_input-prompts/"
+skip_base_zip_dir = true
+zip_file_path = "res://kenney_input-prompts.zip"
+metadata/_custom_type_script = "uid://bqu3bc0tttrfk"
+
+[node name="SkipInstallationDialog" type="ConfirmationDialog" parent="." unique_id=1861126542]
+title = "Skip Installation?"
+initial_position = 2
+size = Vector2i(682, 160)
+ok_button_text = "Skip"
+dialog_text = "The input prompts pack appears to already be installed.
+
+Do you want to force a reinstall of the pack, or skip to picking a style?"
+cancel_button_text = "Reinstall"
+
+[node name="KenneyInputPromptsDialog" parent="." unique_id=257967393 instance=ExtResource("1_pslk0")]
+visible = false
+
+[node name="InstallingDialog" type="AcceptDialog" parent="." unique_id=1047580895]
+title = "Installing..."
+initial_position = 2
+size = Vector2i(400, 100)
+
+[node name="MarginContainer" type="MarginContainer" parent="InstallingDialog" unique_id=1283250877]
+offset_left = 4.0
+offset_top = 4.0
+offset_right = 396.0
+offset_bottom = 96.0
+theme_override_constants/margin_left = 16
+theme_override_constants/margin_top = 16
+theme_override_constants/margin_right = 16
+theme_override_constants/margin_bottom = 16
+
+[node name="VBoxContainer" type="VBoxContainer" parent="InstallingDialog/MarginContainer" unique_id=1482728160]
+layout_mode = 2
+alignment = 1
+
+[node name="StageLabel" type="Label" parent="InstallingDialog/MarginContainer/VBoxContainer" unique_id=891187745]
+unique_name_in_owner = true
+layout_mode = 2
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="ProgressBar" type="ProgressBar" parent="InstallingDialog/MarginContainer/VBoxContainer" unique_id=356766981]
+unique_name_in_owner = true
+layout_mode = 2
+max_value = 1.0
+step = 0.001
+
+[node name="CleanUpDialog" type="ConfirmationDialog" parent="." unique_id=1542805390]
+auto_translate_mode = 1
+title = "Clean Up Extra Content?"
+initial_position = 2
+size = Vector2i(1024, 210)
+ok_button_text = "Yes"
+dialog_text = "Kenney's Input Prompts contains extra content not used by the input remapping menu.
+
+This includes icons for devices not currently detected and preview images. Removing the extras cuts the total size of extracted assets by almost 50%. The option to change input icon styles will remain available after the clean up, too.
+
+Would you like to have the extra content removed?"
+dialog_autowrap = true
+cancel_button_text = "No"
+
+[node name="ErrorDialog" type="AcceptDialog" parent="." unique_id=925144911]
+title = "Error!"
+initial_position = 2
+size = Vector2i(400, 128)
+
+[connection signal="run_completed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_completed"]
+[connection signal="run_failed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_failed"]
+[connection signal="canceled" from="SkipInstallationDialog" to="." method="_on_skip_installation_dialog_canceled"]
+[connection signal="confirmed" from="SkipInstallationDialog" to="." method="_on_skip_installation_dialog_confirmed"]
+[connection signal="canceled" from="KenneyInputPromptsDialog" to="." method="_on_kenney_input_prompts_dialog_canceled"]
+[connection signal="configuration_selected" from="KenneyInputPromptsDialog" to="." method="_on_kenney_input_prompts_dialog_configuration_selected"]
+[connection signal="confirmed" from="KenneyInputPromptsDialog" to="." method="_on_kenney_input_prompts_dialog_confirmed"]
+[connection signal="canceled" from="CleanUpDialog" to="." method="_on_clean_up_dialog_canceled"]
+[connection signal="confirmed" from="CleanUpDialog" to="." method="_on_clean_up_dialog_confirmed"]
+[connection signal="canceled" from="ErrorDialog" to="." method="_on_error_dialog_canceled"]
+[connection signal="confirmed" from="ErrorDialog" to="." method="_on_error_dialog_confirmed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd
new file mode 100644
index 0000000..327d9aa
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd
@@ -0,0 +1,8 @@
+@tool
+extends ConfirmationDialog
+
+const MAIN_SCENE_UPDATE_TEXT = "Would you like to update the project's main scene?\n\nCurrent:\n%s\n\nNew:\n%s\n"
+
+func set_main_scene_text(new_scene_path):
+ var current_scene_path : String = ProjectSettings.get_setting("application/run/main_scene", "")
+ dialog_text = MAIN_SCENE_UPDATE_TEXT % [current_scene_path, new_scene_path]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd.uid
new file mode 100644
index 0000000..35fd6d5
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd.uid
@@ -0,0 +1 @@
+uid://uyh03f60qvd2
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.tscn
new file mode 100644
index 0000000..1c22277
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/main_scene_confirmation_dialog.tscn
@@ -0,0 +1,18 @@
+[gd_scene format=3 uid="uid://b8kr3y0cjxr8m"]
+
+[ext_resource type="Script" uid="uid://uyh03f60qvd2" path="res://addons/maaacks_game_template/installer/main_scene_confirmation_dialog.gd" id="1_1ydgd"]
+
+[node name="MainSceneConfirmationDialog" type="ConfirmationDialog" unique_id=1032555085]
+oversampling_override = 1.0
+title = "Update Main Scene"
+initial_position = 2
+size = Vector2i(1024, 192)
+visible = true
+exclusive = false
+ok_button_text = "Yes"
+dialog_text = "Would you like to update the project's main scene?
+
+"
+dialog_autowrap = true
+cancel_button_text = "No"
+script = ExtResource("1_1ydgd")
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/override.cfg b/evolve-die-repeat/addons/maaacks_game_template/installer/override.cfg
new file mode 100644
index 0000000..8ca7535
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/override.cfg
@@ -0,0 +1,36 @@
+; Project settings override file.
+; Adds gamepad inputs to built-in actions.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+
+[input]
+
+ui_accept={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
+]
+}
+ui_cancel={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":true,"script":null)
+]
+}
+ui_page_up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194323,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null)
+]
+}
+ui_page_down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194324,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null)
+]
+}
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/play_opening_confirmation_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/play_opening_confirmation_dialog.tscn
new file mode 100644
index 0000000..d2d4f6f
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/play_opening_confirmation_dialog.tscn
@@ -0,0 +1,14 @@
+[gd_scene format=3 uid="uid://b8808yj7a0ghj"]
+
+[node name="PlayOpeningConfirmationDialog" type="ConfirmationDialog" unique_id=2132524785]
+oversampling_override = 1.0
+title = "Run & Test"
+initial_position = 2
+size = Vector2i(1024, 148)
+visible = true
+ok_button_text = "Yes"
+dialog_text = "It is recommended to run the opening scene of the plugin and test if any issues occurred during the copying process.
+
+Would you like to run and test the scenes now?"
+dialog_autowrap = true
+cancel_button_text = "No"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/release_notes_label.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/release_notes_label.gd
new file mode 100644
index 0000000..dafbda8
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/release_notes_label.gd
@@ -0,0 +1,79 @@
+@tool
+extends RichTextLabel
+
+const HEADING_STRING_REPLACEMENT = "$1[font_size=%d]$2[/font_size]"
+const BOLD_HEADING_STRING_REPLACEMENT = "$1[b][font_size=%d]$2[/font_size][/b]"
+
+@export_group("Font Sizes")
+@export var h1_font_size: int
+@export var h2_font_size: int
+@export var h3_font_size: int
+@export var h4_font_size: int
+@export var h5_font_size: int
+@export var h6_font_size: int
+@export var bold_headings : bool
+@export_group("Extra Options")
+@export var disable_images : bool = false
+@export var disable_urls : bool = false
+@export var disable_bolds : bool = false
+
+func regex_replace_imgs(markdown_text:String) -> String:
+ var regex = RegEx.new()
+ var match_string := "
"
+ var replace_string := ""
+ if not disable_images:
+ replace_string = "$1"
+ regex.compile(match_string)
+ return regex.sub(markdown_text, replace_string, true)
+
+func regex_replace_urls(markdown_text:String) -> String:
+ var regex = RegEx.new()
+ var match_string := "(https:\\/\\/.*)($|\\s)"
+ var replace_string := "$1"
+ if not disable_urls:
+ replace_string = "[url=$1]$1[/url]"
+ regex.compile(match_string)
+ return regex.sub(markdown_text, replace_string, true)
+
+func regex_replace_bolds(markdown_text:String) -> String:
+ var regex = RegEx.new()
+ var match_string := "\\*\\*(.*)\\*\\*"
+ var replace_string := "$1"
+ if not disable_bolds:
+ replace_string = "[b]$1[/b]"
+ regex.compile(match_string)
+ return regex.sub(markdown_text, replace_string, true)
+
+func regex_replace_titles(markdown_text:String) -> String:
+ var iter = 0
+ var heading_font_sizes : Array[int] = [
+ h1_font_size,
+ h2_font_size,
+ h3_font_size,
+ h4_font_size,
+ h5_font_size,
+ h6_font_size]
+ for heading_font_size in heading_font_sizes:
+ iter += 1
+ var regex = RegEx.new()
+ var match_string : String = "([^#]|^)#{%d}\\s([^\n]*)" % iter
+ var replace_string := HEADING_STRING_REPLACEMENT % [heading_font_size]
+ if bold_headings:
+ replace_string = BOLD_HEADING_STRING_REPLACEMENT % [heading_font_size]
+ regex.compile(match_string)
+ markdown_text = regex.sub(markdown_text, replace_string, true)
+ return markdown_text
+
+func from_release_notes(markdown_text : String) -> void:
+ markdown_text = regex_replace_imgs(markdown_text)
+ markdown_text = regex_replace_urls(markdown_text)
+ markdown_text = regex_replace_bolds(markdown_text)
+ markdown_text = regex_replace_titles(markdown_text)
+ text = markdown_text
+
+func _on_meta_clicked(meta: String) -> void:
+ if meta.begins_with("https://"):
+ var _err = OS.shell_open(meta)
+
+func _ready() -> void:
+ meta_clicked.connect(_on_meta_clicked)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/release_notes_label.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/release_notes_label.gd.uid
new file mode 100644
index 0000000..aaa7229
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/release_notes_label.gd.uid
@@ -0,0 +1 @@
+uid://dab5i53lgv34a
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/setup_complete_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_complete_dialog.tscn
new file mode 100644
index 0000000..72f0b99
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_complete_dialog.tscn
@@ -0,0 +1,14 @@
+[gd_scene format=3 uid="uid://bivpkn7n06h1e"]
+
+[node name="SetupCompleteDialog" type="AcceptDialog" unique_id=475849823]
+oversampling_override = 1.0
+title = "Setup Complete!"
+initial_position = 2
+size = Vector2i(1032, 360)
+visible = true
+dialog_text = "Thanks for installing Maaack's Game Template!
+
+Go to `Project > Tools > Run Maaack's Game Template Setup...` for extra features.
+Each step of the setup can also be revisited.
+
+See the included README for next steps and additional documentation."
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.gd
new file mode 100644
index 0000000..6dd179a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.gd
@@ -0,0 +1,139 @@
+@tool
+extends AcceptDialog
+
+@export_file("*.tscn") var check_version_scene_path : String
+@export_dir var input_prompts_directory_path : String
+
+@onready var plugin_label : Label = %PluginLabel
+@onready var update_label : Label = %UpdateLabel
+@onready var update_check_box : CheckBox = %UpdateCheckBox
+@onready var update_button : Button = %UpdateButton
+@onready var copy_check_box : CheckBox = %CopyCheckBox
+@onready var copy_button : Button = %CopyButton
+@onready var delete_check_box : CheckBox = %DeleteCheckBox
+@onready var delete_button : Button = %DeleteButton
+@onready var update_paths_check_box : CheckBox = %UpdatePathsCheckBox
+@onready var update_paths_button : Button = %UpdatePathsButton
+@onready var set_main_scene_check_box : CheckBox = %SetMainSceneCheckBox
+@onready var set_main_scene_button : Button = %SetMainSceneButton
+@onready var set_default_theme_check_box : CheckBox = %SetDefaultThemeCheckBox
+@onready var set_default_theme_button : Button = %SetDefaultThemeButton
+@onready var add_input_icons_check_box : CheckBox = %AddInputIconsCheckBox
+@onready var add_input_icons_button : Button = %AddInputIconsButton
+
+func _refresh_plugin_details() -> void:
+ for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
+ if enabled_plugin.contains(MaaacksGameTemplatePlugin.get_settings_path()):
+ var config := ConfigFile.new()
+ var error = config.load(enabled_plugin)
+ if error != OK:
+ return
+ var current_plugin_version : String = config.get_value("plugin", "version", "0.0.0")
+ var plugin_name : String = config.get_value("plugin", "name", "Plugin")
+ plugin_label.text = "%s v%s" % [plugin_name, current_plugin_version]
+
+func _show_plugin_versions_match() -> void:
+ update_label.text = "Using Latest Version"
+ update_check_box.button_pressed = true
+ update_button.disabled = true
+
+func _enable_update_plugin_tool_option(tag_name : String) -> void:
+ update_label.text = "Update to Latest Version v%s" % tag_name
+ update_button.disabled = false
+
+func _open_check_plugin_version() -> void:
+ if check_version_scene_path.is_empty():
+ push_warning("Variable \"check_version_scene_path\" is not set")
+ return
+ if ProjectSettings.get_setting(MaaacksGameTemplatePlugin.get_settings_path() + "disable_update_check", false):
+ update_label.text = "Check for Latest Version"
+ update_button.disabled = false
+ return
+ var check_version_scene : PackedScene = load(check_version_scene_path)
+ var check_version_instance : Node = check_version_scene.instantiate()
+ check_version_instance.auto_start = true
+ check_version_instance.new_version_detected.connect(_enable_update_plugin_tool_option)
+ check_version_instance.versions_matched.connect(_show_plugin_versions_match)
+ add_child(check_version_instance)
+
+func _refresh_copy_and_delete_examples() -> void:
+ var examples_path = MaaacksGameTemplatePlugin.instance.get_plugin_examples_path()
+ if MaaacksGameTemplatePlugin.get_copy_path() != examples_path:
+ copy_check_box.button_pressed = true
+ var dir := DirAccess.open("res://")
+ if dir.dir_exists(examples_path):
+ copy_button.disabled = false
+ delete_button.disabled = false
+ else:
+ delete_check_box.button_pressed = true
+
+func _refresh_update_autoload_paths() -> void:
+ update_paths_check_box.button_pressed = MaaacksGameTemplatePlugin.instance.are_autoload_paths_updated()
+ update_paths_button.disabled = false
+
+func _refresh_main_scene() -> void:
+ if MaaacksGameTemplatePlugin.instance.is_main_scene_set():
+ set_main_scene_check_box.button_pressed = true
+ else:
+ set_main_scene_button.disabled = false
+
+func _refresh_default_theme() -> void:
+ set_default_theme_button.disabled = false
+ if ProjectSettings.get_setting("gui/theme/custom", "") != "":
+ set_default_theme_check_box.button_pressed = true
+
+func _refresh_input_prompts() -> void:
+ if input_prompts_directory_path.is_empty():
+ push_warning("Variable \"input_prompts_directory_path\" is not set")
+ return
+ if DirAccess.dir_exists_absolute(input_prompts_directory_path):
+ add_input_icons_check_box.button_pressed = true
+ add_input_icons_button.disabled = false
+
+func _refresh_options():
+ _refresh_plugin_details()
+ _open_check_plugin_version()
+ _refresh_copy_and_delete_examples()
+ _refresh_update_autoload_paths()
+ _refresh_main_scene()
+ _refresh_default_theme()
+ _refresh_input_prompts()
+
+func _ready():
+ _refresh_options()
+
+func _on_update_button_pressed():
+ if ProjectSettings.get_setting(MaaacksGameTemplatePlugin.get_settings_path() + "disable_update_check", false):
+ ProjectSettings.set_setting(MaaacksGameTemplatePlugin.get_settings_path() + "disable_update_check", false)
+ _open_check_plugin_version()
+ return
+ else:
+ tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_update_plugin())
+ queue_free()
+
+func _on_copy_button_pressed():
+ tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_copy_and_edit_dialog())
+ queue_free()
+
+func _on_delete_button_pressed():
+ tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_delete_examples_short_confirmation_dialog())
+ queue_free()
+
+func _on_update_paths_button_pressed():
+ MaaacksGameTemplatePlugin.instance.update_autoload_paths(MaaacksGameTemplatePlugin.get_copy_path())
+ _refresh_update_autoload_paths()
+ update_paths_button.disabled = true
+ await get_tree().create_timer(1.0).timeout
+ update_paths_button.disabled = false
+
+func _on_set_main_scene_button_pressed():
+ tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_main_scene_confirmation_dialog(MaaacksGameTemplatePlugin.get_copy_path()))
+ queue_free()
+
+func _on_set_default_theme_button_pressed():
+ tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_theme_selection_dialog(MaaacksGameTemplatePlugin.get_copy_path()))
+ queue_free()
+
+func _on_add_input_icons_button_pressed():
+ tree_exited.connect(func(): MaaacksGameTemplatePlugin.instance.open_input_icons_dialog())
+ queue_free()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.gd.uid
new file mode 100644
index 0000000..8261eef
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.gd.uid
@@ -0,0 +1 @@
+uid://coq26stqrim51
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.tscn
new file mode 100644
index 0000000..995222a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/setup_wizard.tscn
@@ -0,0 +1,171 @@
+[gd_scene format=3 uid="uid://uojlvirhbysk"]
+
+[ext_resource type="Script" uid="uid://coq26stqrim51" path="res://addons/maaacks_game_template/installer/setup_wizard.gd" id="1_jck5m"]
+
+[node name="SetupWizardDialog" type="AcceptDialog" unique_id=1285990768]
+oversampling_override = 1.0
+title = "Setup Wizard"
+initial_position = 2
+size = Vector2i(576, 540)
+visible = true
+ok_button_text = "Done"
+dialog_autowrap = true
+script = ExtResource("1_jck5m")
+check_version_scene_path = "res://addons/maaacks_game_template/installer/check_plugin_version.tscn"
+input_prompts_directory_path = "res://assets/kenney_input-prompts/"
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1021110393]
+custom_minimum_size = Vector2(560, 483)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 8.0
+offset_top = 8.0
+offset_right = -8.0
+offset_bottom = -49.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 16
+
+[node name="PluginLabel" type="Label" parent="VBoxContainer" unique_id=605834834]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(384, 0)
+layout_mode = 2
+autowrap_mode = 3
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer" unique_id=1703792217]
+layout_mode = 2
+
+[node name="StepsContainer" type="GridContainer" parent="VBoxContainer" unique_id=522914785]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/h_separation = 12
+columns = 3
+
+[node name="UpdateLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=1861535200]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Update to Latest Version"
+
+[node name="UpdateCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=760708479]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="UpdateButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=238784120]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[node name="CopyLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=1894223639]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Copy Example Files"
+
+[node name="CopyCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=757403442]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="CopyButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=2087636683]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[node name="DeleteLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=1855034900]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Delete Example Files"
+
+[node name="DeleteCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=952196576]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="DeleteButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=391293741]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[node name="UpdatePathsLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=641573803]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Update Autoload Paths"
+
+[node name="UpdatePathsCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=818510729]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="UpdatePathsButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=601414355]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[node name="SetMainSceneLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=485048782]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Set the Main Scene"
+
+[node name="SetMainSceneCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=1845563704]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="SetMainSceneButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=1575389523]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[node name="SetDefaultThemeLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=1847430117]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Set the Default Theme"
+
+[node name="SetDefaultThemeCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=1791449081]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="SetDefaultThemeButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=1924403540]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[node name="AddInputIconsLabel" type="Label" parent="VBoxContainer/StepsContainer" unique_id=26778455]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Add Input Prompt Icons"
+
+[node name="AddInputIconsCheckBox" type="CheckBox" parent="VBoxContainer/StepsContainer" unique_id=944329794]
+unique_name_in_owner = true
+layout_mode = 2
+disabled = true
+
+[node name="AddInputIconsButton" type="Button" parent="VBoxContainer/StepsContainer" unique_id=1144416964]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(100, 0)
+layout_mode = 2
+disabled = true
+text = "Run"
+
+[connection signal="pressed" from="VBoxContainer/StepsContainer/UpdateButton" to="." method="_on_update_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/StepsContainer/CopyButton" to="." method="_on_copy_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/StepsContainer/DeleteButton" to="." method="_on_delete_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/StepsContainer/UpdatePathsButton" to="." method="_on_update_paths_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/StepsContainer/SetMainSceneButton" to="." method="_on_set_main_scene_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/StepsContainer/SetDefaultThemeButton" to="." method="_on_set_default_theme_button_pressed"]
+[connection signal="pressed" from="VBoxContainer/StepsContainer/AddInputIconsButton" to="." method="_on_add_input_icons_button_pressed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.gd
new file mode 100644
index 0000000..db6beb7
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.gd
@@ -0,0 +1,33 @@
+@tool
+extends ConfirmationDialog
+
+signal theme_selected(theme_file: String)
+
+@export_dir var theme_directories : Array[String] :
+ set(value):
+ theme_directories = value
+ if is_inside_tree():
+ %FileLister.directories = theme_directories
+ _fill_with_themes()
+
+func _fill_with_themes() -> void:
+ %ItemList.clear()
+ for file in %FileLister.files:
+ if file is String:
+ var readable_name = file.get_file().get_basename().capitalize()
+ %ItemList.add_item(readable_name)
+
+func _ready() -> void:
+ get_ok_button().disabled = true
+
+func _preview_theme(theme_file: String) -> void:
+ var theme_resource : Theme = load(theme_file)
+ if theme_resource == null: return
+ %ThemePreviewContainer.theme = theme_resource
+
+func _on_item_list_item_selected(index) -> void:
+ get_ok_button().disabled = false
+ if index < %FileLister.files.size():
+ var file = %FileLister.files[index]
+ _preview_theme(file)
+ theme_selected.emit(file)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.gd.uid
new file mode 100644
index 0000000..61f64df
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.gd.uid
@@ -0,0 +1 @@
+uid://c6p8xjvlrgsfk
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.tscn
new file mode 100644
index 0000000..f020e11
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/theme_selection_dialog.tscn
@@ -0,0 +1,179 @@
+[gd_scene format=3 uid="uid://d25mdvmlbn35"]
+
+[ext_resource type="Script" uid="uid://c6p8xjvlrgsfk" path="res://addons/maaacks_game_template/installer/theme_selection_dialog.gd" id="1_5u0gx"]
+[ext_resource type="Script" uid="uid://bij7wsh8d44gv" path="res://addons/maaacks_game_template/base/nodes/utilities/file_lister.gd" id="2_luhgx"]
+
+[node name="ThemeSelectionDialog" type="ConfirmationDialog" unique_id=1923085742]
+oversampling_override = 1.0
+title = "Use a Starter Theme"
+initial_position = 2
+size = Vector2i(1024, 704)
+visible = true
+ok_button_text = "Yes"
+dialog_autowrap = true
+cancel_button_text = "No"
+script = ExtResource("1_5u0gx")
+
+[node name="FileLister" type="Node" parent="." unique_id=105896521]
+unique_name_in_owner = true
+script = ExtResource("2_luhgx")
+ends_with = ".tres"
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=769564059]
+custom_minimum_size = Vector2(560, 443)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 8.0
+offset_top = 8.0
+offset_right = -8.0
+offset_bottom = -49.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 16
+
+[node name="Label" type="Label" parent="VBoxContainer" unique_id=804286205]
+custom_minimum_size = Vector2(384, 0)
+layout_mode = 2
+text = "Set the projects default theme to a start theme provided below. These can be customized as needed.
+
+Requires restarting the editor to take full effect."
+autowrap_mode = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer" unique_id=1467622109]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="ItemList" type="ItemList" parent="VBoxContainer/HBoxContainer" unique_id=1867843362]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VSeparator" type="VSeparator" parent="VBoxContainer/HBoxContainer" unique_id=760795920]
+layout_mode = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer" unique_id=743082645]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_constants/margin_left = 16
+theme_override_constants/margin_right = 16
+
+[node name="ThemePreviewContainer" type="TabContainer" parent="VBoxContainer/HBoxContainer/MarginContainer" unique_id=1589276431]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+current_tab = 0
+
+[node name="Tab1" type="Control" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer" unique_id=126620399]
+layout_mode = 2
+metadata/_tab_index = 0
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1" unique_id=1265284030]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer" unique_id=1738445662]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer" unique_id=1370570709]
+layout_mode = 2
+text = "Label"
+horizontal_alignment = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer" unique_id=2037425090]
+layout_mode = 2
+
+[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer/HBoxContainer" unique_id=2137217914]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Button"
+
+[node name="Button2" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer/HBoxContainer" unique_id=1703701708]
+layout_mode = 2
+size_flags_horizontal = 3
+toggle_mode = true
+button_pressed = true
+text = "Button"
+
+[node name="Button3" type="Button" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer/HBoxContainer" unique_id=2138904310]
+layout_mode = 2
+size_flags_horizontal = 3
+disabled = true
+text = "Button"
+
+[node name="CheckButton" type="CheckButton" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer" unique_id=1219421491]
+layout_mode = 2
+text = "CheckButton"
+
+[node name="CheckBox" type="CheckBox" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer" unique_id=1689998241]
+layout_mode = 2
+text = "CheckBox"
+
+[node name="MenuButton" type="MenuButton" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer" unique_id=2023390786]
+layout_mode = 2
+text = "MenuButton"
+
+[node name="OptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab1/MarginContainer/VBoxContainer" unique_id=178947235]
+layout_mode = 2
+selected = 0
+item_count = 2
+popup/item_0/text = "OptionButton"
+popup/item_0/id = 0
+popup/item_1/text = "OptionButton2"
+popup/item_1/id = 1
+
+[node name="Tab2" type="Control" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer" unique_id=1118675129]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 1
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2" unique_id=88049233]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer" unique_id=1700688968]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=1891786524]
+layout_mode = 2
+text = "Another label"
+horizontal_alignment = 1
+
+[node name="LineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=1149432987]
+layout_mode = 2
+
+[node name="TextEdit" type="TextEdit" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=1224468134]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="HSlider" type="HSlider" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=1806059044]
+layout_mode = 2
+
+[node name="HScrollBar" type="HScrollBar" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=2059385849]
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=451515291]
+layout_mode = 2
+
+[node name="ProgressBar" type="ProgressBar" parent="VBoxContainer/HBoxContainer/MarginContainer/ThemePreviewContainer/Tab2/MarginContainer/VBoxContainer" unique_id=1218906396]
+layout_mode = 2
+value = 50.0
+
+[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ItemList" to="." method="_on_item_list_item_selected"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.gd b/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.gd
new file mode 100644
index 0000000..604f223
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.gd
@@ -0,0 +1,174 @@
+@tool
+extends Node
+## Script for updating the version of a plugin to the latest release on GitHub.
+
+signal update_completed
+
+const DownloadAndExtract = MaaacksGameTemplatePlugin.DownloadAndExtract
+const APIClient = MaaacksGameTemplatePlugin.APIClient
+const ReleaseNotesLabel = preload("./release_notes_label.gd")
+
+const API_RELEASES_URL := "https://api.github.com/repos/%s/%s/releases"
+const UPDATE_CONFIRMATION_MESSAGE := "This will update the contents of the plugin folder (addons/%s/).\nFiles outside of the plugin folder will not be affected.\n\nUpdate %s to v%s?"
+const PLUGIN_EXTRACT_PATH := "res://addons/%s/"
+const PLUGIN_TEMP_ZIP_PATH := "res://%s_%s_update.zip"
+
+## The directory of the plugin to update. Typically in res://addons/.
+@export var plugin_directory : String
+## The URL of the GitHub repo to pull new releases.
+@export var plugin_github_url : String :
+ set(value):
+ plugin_github_url = value
+ _update_urls()
+@export_group("Advanced")
+## If true, automatically download the new version when ready.
+@export var auto_start : bool = false
+## Text to remove from the tag before showing to the user.
+@export var replace_tag_name : String = "v"
+## The default lowest version to display.
+@export var default_version : String = "0.0.0"
+## If true, test getting the new version.
+## Replace with @export_tool_button for Godot 4.4+
+@export var _test_action : bool = false :
+ set(value):
+ if value and Engine.is_editor_hint():
+ get_newest_version()
+
+@onready var _api_client : APIClient = $APIClient
+@onready var _download_and_extract_node : DownloadAndExtract = $DownloadAndExtract
+@onready var _update_confirmation_dialog : ConfirmationDialog = $UpdateConfirmationDialog
+@onready var _installing_dialog : AcceptDialog = $InstallingDialog
+@onready var _error_dialog : AcceptDialog = $ErrorDialog
+@onready var _success_dialog : AcceptDialog = $SuccessDialog
+@onready var _release_notes_label : ReleaseNotesLabel = %ReleaseNotesLabel
+@onready var _update_label : Label = %UpdateLabel
+@onready var _warning_message_button : LinkButton = %WarningMessageButton
+@onready var _warning_message_label : Label = %WarningMessageLabel
+@onready var _release_notes_button : LinkButton = %ReleaseNotesButton
+@onready var _release_notes_panel : Panel = %ReleaseNotesPanel
+@onready var _stage_label : Label = %StageLabel
+@onready var _progress_bar : ProgressBar = %ProgressBar
+
+var _zipball_url : String
+var _newest_version : String
+var _plugin_name : String
+var _current_plugin_version : String
+
+func _load_plugin_details() -> void:
+ if plugin_directory.is_empty(): return
+ for enabled_plugin in ProjectSettings.get_setting("editor_plugins/enabled"):
+ if enabled_plugin.contains(plugin_directory):
+ var config := ConfigFile.new()
+ var error = config.load(enabled_plugin)
+ if error != OK:
+ return
+ _current_plugin_version = config.get_value("plugin", "version", default_version)
+ _plugin_name = config.get_value("plugin", "name", "Plugin")
+
+func _update_urls() -> void:
+ if plugin_github_url.is_empty(): return
+ if _api_client == null: return
+ var regex := RegEx.create_from_string("https:\\/\\/github\\.com\\/([\\w-]+)\\/([\\w-]+)\\/*")
+ var regex_match := regex.search(plugin_github_url)
+ if regex_match == null: return
+ var username := regex_match.get_string(1)
+ var repository := regex_match.get_string(2)
+ _api_client.api_url = API_RELEASES_URL % [username, repository]
+
+func _show_error_dialog(error : String) -> void:
+ _error_dialog.show()
+ _error_dialog.dialog_text = "%s!" % error
+
+func _show_success_dialog() -> void:
+ _success_dialog.show()
+ _success_dialog.dialog_text = "%s updated to v%s." % [_plugin_name, _newest_version]
+
+func _on_api_client_request_failed(error : String) -> void:
+ _show_error_dialog(error)
+
+func _on_api_client_response_received(response_body : Variant) -> void:
+ if response_body is not Array:
+ push_error("Response was not an array")
+ return
+ if response_body.is_empty():
+ push_error("Response was an empty array")
+ return
+ var latest_release : Dictionary = response_body.front()
+ _newest_version = default_version
+ if latest_release.has("tag_name"):
+ var tag_name = latest_release["tag_name"]
+ if replace_tag_name:
+ tag_name = tag_name.replacen(replace_tag_name, "")
+ _newest_version = tag_name
+ if latest_release.has("zipball_url"):
+ _zipball_url = latest_release["zipball_url"]
+ _download_and_extract_node.zip_url = _zipball_url
+ _download_and_extract_node.zip_file_path = PLUGIN_TEMP_ZIP_PATH % [plugin_directory, _newest_version]
+ _update_label.text = UPDATE_CONFIRMATION_MESSAGE % [plugin_directory, _plugin_name, _newest_version]
+ if latest_release.has("body"):
+ _release_notes_label.from_release_notes(latest_release["body"])
+ _update_confirmation_dialog.show()
+
+func _on_download_and_extract_zip_saved() -> void:
+ OS.move_to_trash(ProjectSettings.globalize_path(PLUGIN_EXTRACT_PATH % plugin_directory))
+
+func _on_download_and_extract_run_failed(error : String) -> void:
+ _show_error_dialog(error)
+
+func _on_download_and_extract_run_completed() -> void:
+ update_completed.emit()
+ _show_success_dialog()
+
+func _on_error_dialog_canceled() -> void:
+ queue_free()
+
+func _on_error_dialog_confirmed() -> void:
+ queue_free()
+
+func _on_success_dialog_canceled() -> void:
+ queue_free()
+
+func _on_success_dialog_confirmed() -> void:
+ queue_free()
+
+func _on_update_confirmation_dialog_canceled() -> void:
+ queue_free()
+
+func _on_update_confirmation_dialog_confirmed() -> void:
+ _download_and_extract_node.run()
+ _installing_dialog.show()
+
+func _on_warning_message_button_pressed():
+ _warning_message_label.show()
+ _warning_message_button.hide()
+
+func _on_release_notes_button_pressed() -> void:
+ _release_notes_panel.show()
+ _release_notes_button.hide()
+
+func get_newest_version() -> void:
+ _api_client.request()
+
+func _ready() -> void:
+ _load_plugin_details()
+ _update_confirmation_dialog.hide()
+ _installing_dialog.hide()
+ _error_dialog.hide()
+ _success_dialog.hide()
+ if auto_start:
+ get_newest_version()
+
+func _process(_delta : float) -> void:
+ if _installing_dialog.visible:
+ _progress_bar.value = _download_and_extract_node.get_progress()
+ match _download_and_extract_node.stage:
+ DownloadAndExtract.DownloadAndExtractStage.DOWNLOAD:
+ _stage_label.text = "Downloading..."
+ DownloadAndExtract.DownloadAndExtractStage.SAVE:
+ _stage_label.text = "Saving..."
+ DownloadAndExtract.DownloadAndExtractStage.EXTRACT:
+ _stage_label.text = "Extracting..."
+ DownloadAndExtract.DownloadAndExtractStage.DELETE:
+ _stage_label.text = "Cleaning up..."
+ DownloadAndExtract.DownloadAndExtractStage.NONE:
+ _installing_dialog.hide()
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.gd.uid
new file mode 100644
index 0000000..fc72e0a
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.gd.uid
@@ -0,0 +1 @@
+uid://cwj8dpqveao6o
diff --git a/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.tscn b/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.tscn
new file mode 100644
index 0000000..86685a4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/installer/update_plugin.tscn
@@ -0,0 +1,158 @@
+[gd_scene format=3 uid="uid://gynblau0ojia"]
+
+[ext_resource type="Script" uid="uid://cwj8dpqveao6o" path="res://addons/maaacks_game_template/installer/update_plugin.gd" id="1_s6qpc"]
+[ext_resource type="PackedScene" uid="uid://drhhakm62vjsy" path="res://addons/maaacks_game_template/utilities/api_client.tscn" id="2_s6pdq"]
+[ext_resource type="PackedScene" uid="uid://dlkmofxhavh10" path="res://addons/maaacks_game_template/utilities/download_and_extract.tscn" id="3_s6pdq"]
+[ext_resource type="Script" uid="uid://dab5i53lgv34a" path="res://addons/maaacks_game_template/installer/release_notes_label.gd" id="4_1amwf"]
+
+[node name="UpdatePlugin" type="Node" unique_id=393194098]
+script = ExtResource("1_s6qpc")
+plugin_directory = "maaacks_game_template"
+plugin_github_url = "https://github.com/Maaack/Godot-Game-Template"
+
+[node name="APIClient" parent="." unique_id=2005212994 instance=ExtResource("2_s6pdq")]
+api_url = "https://api.github.com/repos/Maaack/Godot-Game-Template/releases"
+request_method = 0
+
+[node name="DownloadAndExtract" parent="." unique_id=841464702 instance=ExtResource("3_s6pdq")]
+extract_path = "res://"
+path_match_string = "addons/"
+skip_base_zip_dir = true
+force = true
+
+[node name="UpdateConfirmationDialog" type="ConfirmationDialog" parent="." unique_id=948040961]
+auto_translate_mode = 1
+title = "Update Plugin?"
+initial_position = 2
+size = Vector2i(640, 360)
+dialog_autowrap = true
+
+[node name="MarginContainer" type="MarginContainer" parent="UpdateConfirmationDialog" unique_id=2098017072]
+offset_left = 8.0
+offset_top = 8.0
+offset_right = 632.0
+offset_bottom = 311.0
+theme_override_constants/margin_bottom = 16
+
+[node name="VBoxContainer" type="VBoxContainer" parent="UpdateConfirmationDialog/MarginContainer" unique_id=1821167294]
+layout_mode = 2
+
+[node name="UpdateLabel" type="Label" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer" unique_id=132762526]
+unique_name_in_owner = true
+layout_mode = 2
+text = "This will update the contents of the plugin folder (addons/plugin_directory/).
+Files outside of the plugin folder will not be affected.
+
+Update to v0.0.0?"
+
+[node name="HSeparator" type="HSeparator" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer" unique_id=929196357]
+layout_mode = 2
+
+[node name="WarningMessageButton" type="LinkButton" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer" unique_id=1658098735]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Warning About Updating"
+
+[node name="WarningMessageLabel" type="Label" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer" unique_id=254491516]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(624, 205)
+layout_mode = 2
+text = "Not all updates are backwards compatible!
+
+Updating can cause issues with previously copied examples. It is recommended to delete any copied example files before starting an update, if it is an option. Otherwise, pay attention to the Release Notes for an idea for how changes may impact your project.
+
+Lastly, save your work and proceed with caution."
+autowrap_mode = 3
+
+[node name="ReleaseNotesButton" type="LinkButton" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer" unique_id=1940658271]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Show Release Notes"
+
+[node name="ReleaseNotesPanel" type="Panel" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer" unique_id=726359929]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(0, 420)
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="ReleaseNotesLabel" type="RichTextLabel" parent="UpdateConfirmationDialog/MarginContainer/VBoxContainer/ReleaseNotesPanel" unique_id=914304643]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+focus_mode = 2
+bbcode_enabled = true
+selection_enabled = true
+script = ExtResource("4_1amwf")
+h1_font_size = 64
+h2_font_size = 48
+h3_font_size = 32
+h4_font_size = 24
+h5_font_size = 16
+h6_font_size = 12
+bold_headings = true
+
+[node name="InstallingDialog" type="AcceptDialog" parent="." unique_id=35516267]
+auto_translate_mode = 1
+title = "Installing..."
+initial_position = 2
+size = Vector2i(400, 111)
+
+[node name="MarginContainer" type="MarginContainer" parent="InstallingDialog" unique_id=82754080]
+offset_left = 4.0
+offset_top = 4.0
+offset_right = 396.0
+offset_bottom = 96.0
+theme_override_constants/margin_left = 16
+theme_override_constants/margin_top = 16
+theme_override_constants/margin_right = 16
+theme_override_constants/margin_bottom = 16
+
+[node name="VBoxContainer" type="VBoxContainer" parent="InstallingDialog/MarginContainer" unique_id=1355932689]
+layout_mode = 2
+alignment = 1
+
+[node name="StageLabel" type="Label" parent="InstallingDialog/MarginContainer/VBoxContainer" unique_id=446376319]
+unique_name_in_owner = true
+layout_mode = 2
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="ProgressBar" type="ProgressBar" parent="InstallingDialog/MarginContainer/VBoxContainer" unique_id=1804329300]
+unique_name_in_owner = true
+layout_mode = 2
+max_value = 1.0
+step = 0.001
+
+[node name="ErrorDialog" type="AcceptDialog" parent="." unique_id=99271863]
+auto_translate_mode = 1
+title = "Error!"
+initial_position = 2
+size = Vector2i(400, 128)
+
+[node name="SuccessDialog" type="AcceptDialog" parent="." unique_id=471405123]
+auto_translate_mode = 1
+title = "Update Complete"
+initial_position = 2
+size = Vector2i(400, 128)
+dialog_text = "%s updated to v%s."
+
+[connection signal="request_failed" from="APIClient" to="." method="_on_api_client_request_failed"]
+[connection signal="response_received" from="APIClient" to="." method="_on_api_client_response_received"]
+[connection signal="run_completed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_completed"]
+[connection signal="run_failed" from="DownloadAndExtract" to="." method="_on_download_and_extract_run_failed"]
+[connection signal="zip_saved" from="DownloadAndExtract" to="." method="_on_download_and_extract_zip_saved"]
+[connection signal="canceled" from="UpdateConfirmationDialog" to="." method="_on_update_confirmation_dialog_canceled"]
+[connection signal="confirmed" from="UpdateConfirmationDialog" to="." method="_on_update_confirmation_dialog_confirmed"]
+[connection signal="pressed" from="UpdateConfirmationDialog/MarginContainer/VBoxContainer/WarningMessageButton" to="." method="_on_warning_message_button_pressed"]
+[connection signal="pressed" from="UpdateConfirmationDialog/MarginContainer/VBoxContainer/ReleaseNotesButton" to="." method="_on_release_notes_button_pressed"]
+[connection signal="canceled" from="ErrorDialog" to="." method="_on_error_dialog_canceled"]
+[connection signal="confirmed" from="ErrorDialog" to="." method="_on_error_dialog_confirmed"]
+[connection signal="canceled" from="SuccessDialog" to="." method="_on_success_dialog_canceled"]
+[connection signal="confirmed" from="SuccessDialog" to="." method="_on_success_dialog_confirmed"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/.gdignore b/evolve-die-repeat/addons/maaacks_game_template/media/.gdignore
new file mode 100644
index 0000000..e69de29
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/authorize_butler.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/authorize_butler.png
new file mode 100644
index 0000000..bb26b5f
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/authorize_butler.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/github-secrets.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/github-secrets.png
new file mode 100644
index 0000000..8e1ef40
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/github-secrets.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/github-variables.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/github-variables.png
new file mode 100644
index 0000000..fbfd71a
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/github-variables.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/godot_export.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/godot_export.png
new file mode 100644
index 0000000..52b112b
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/godot_export.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_html_missing.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_html_missing.png
new file mode 100644
index 0000000..d693c6d
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_html_missing.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_html_setting.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_html_setting.png
new file mode 100644
index 0000000..f33c999
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_html_setting.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_kind_of_project.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_kind_of_project.png
new file mode 100644
index 0000000..625949f
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_kind_of_project.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_playable.png b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_playable.png
new file mode 100644
index 0000000..a015735
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/build-and-publish/itch_playable.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/credits_scene-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/credits_scene-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..42fa46f
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/credits_scene-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/game-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/game-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..ea70a32
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/game-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/gwj-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/gwj-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..598f426
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/gwj-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/input_remapping-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/input_remapping-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..6e8dd7d
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/input_remapping-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/maaack-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/maaack-black-transparent-256x256.png
new file mode 100644
index 0000000..23523f6
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/maaack-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/maaacks-plugin-suite-256x256.gif b/evolve-die-repeat/addons/maaacks_game_template/media/maaacks-plugin-suite-256x256.gif
new file mode 100644
index 0000000..d5ab8fb
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/maaacks-plugin-suite-256x256.gif differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/menus-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/menus-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..531ffaa
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/menus-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/music_controller-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/music_controller-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..0e7e1dc
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/music_controller-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/options-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/options-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..ca674b9
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/options-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/scene_loader-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/scene_loader-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..a40ee1b
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/scene_loader-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-juliocacko-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-juliocacko-1.png
new file mode 100644
index 0000000..f9b609d
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-juliocacko-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-juliocacko-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-juliocacko-2.png
new file mode 100644
index 0000000..51da311
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-juliocacko-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-1.png
new file mode 100644
index 0000000..8286e7f
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-2.png
new file mode 100644
index 0000000..ba8455c
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-3.png
new file mode 100644
index 0000000..ad1eebc
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-4.png
new file mode 100644
index 0000000..19c2343
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-kenney-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-xelu-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-xelu-1.png
new file mode 100644
index 0000000..157614c
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-xelu-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-xelu-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-xelu-2.png
new file mode 100644
index 0000000..acb0964
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-5-xelu-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-1.png
new file mode 100644
index 0000000..d5d504c
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-2.png
new file mode 100644
index 0000000..0020cd7
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-3.png
new file mode 100644
index 0000000..cb10028
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-4.png
new file mode 100644
index 0000000..02ccf47
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-5.png
new file mode 100644
index 0000000..afae5fa
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-6.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-6.png
new file mode 100644
index 0000000..a2a0161
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-audio-options-6.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-1.png
new file mode 100644
index 0000000..13b9478
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-2.png
new file mode 100644
index 0000000..c9c6936
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-3.png
new file mode 100644
index 0000000..d1652b0
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-4.png
new file mode 100644
index 0000000..8e649cd
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-5.png
new file mode 100644
index 0000000..f882582
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-6.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-6.png
new file mode 100644
index 0000000..89d9cad
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-6.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-7.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-7.png
new file mode 100644
index 0000000..37dc102
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-7.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-8.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-8.png
new file mode 100644
index 0000000..35253e0
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-8.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-9.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-9.png
new file mode 100644
index 0000000..c863208
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-list-9.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-1.png
new file mode 100644
index 0000000..e308813
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-2.png
new file mode 100644
index 0000000..6236ced
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-3.png
new file mode 100644
index 0000000..6e42860
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-4.png
new file mode 100644
index 0000000..e4f1377
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-5.png
new file mode 100644
index 0000000..9a6c9cb
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-sensitivity-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-1.png
new file mode 100644
index 0000000..cb187d8
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-2.png
new file mode 100644
index 0000000..32e03ff
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-3.png
new file mode 100644
index 0000000..b7c0305
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-4.png
new file mode 100644
index 0000000..062d982
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-5.png
new file mode 100644
index 0000000..af86c6c
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-input-tree-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-1.png
new file mode 100644
index 0000000..eb1e243
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-2.png
new file mode 100644
index 0000000..5c8ecdb
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-3.png
new file mode 100644
index 0000000..61b3583
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-lost-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-select-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-select-1.png
new file mode 100644
index 0000000..1bc5dd8
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-select-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-1.png
new file mode 100644
index 0000000..69e11a1
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-2.png
new file mode 100644
index 0000000..69fab0b
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-3.png
new file mode 100644
index 0000000..f90392c
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-state-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-1.png
new file mode 100644
index 0000000..b1ce24f
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-2.png
new file mode 100644
index 0000000..d9042d1
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-3.png
new file mode 100644
index 0000000..d0a02f0
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-level-won-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-1.png
new file mode 100644
index 0000000..789ed03
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-2.png
new file mode 100644
index 0000000..7aefce7
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-3.png
new file mode 100644
index 0000000..ecaa3ed
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-4.png
new file mode 100644
index 0000000..56e40c0
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-loading-screen-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-1.png
new file mode 100644
index 0000000..ebbfeff
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-2.png
new file mode 100644
index 0000000..6aa9637
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-3.png
new file mode 100644
index 0000000..b7f6caa
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-4.png
new file mode 100644
index 0000000..e5285ab
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-5.png
new file mode 100644
index 0000000..871aeb7
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-main-menu-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-mini-options-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-mini-options-1.png
new file mode 100644
index 0000000..57e1e58
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-mini-options-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-mini-options-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-mini-options-2.png
new file mode 100644
index 0000000..22c5133
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-mini-options-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-1.png
new file mode 100644
index 0000000..2e2a30b
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-2.png
new file mode 100644
index 0000000..5e30fa2
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-3.png
new file mode 100644
index 0000000..795eac5
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-4.png
new file mode 100644
index 0000000..c248cf1
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-5.png
new file mode 100644
index 0000000..5cb7c36
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-pause-menu-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-1.png
new file mode 100644
index 0000000..cac8c8e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-10.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-10.png
new file mode 100644
index 0000000..989ebcf
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-10.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-11.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-11.png
new file mode 100644
index 0000000..c970f2e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-11.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-12.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-12.png
new file mode 100644
index 0000000..50d2728
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-12.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-2.png
new file mode 100644
index 0000000..ccbcf1a
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-3.png
new file mode 100644
index 0000000..6c696fa
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-4.png
new file mode 100644
index 0000000..63bc601
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-5.png
new file mode 100644
index 0000000..a5f85a3
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-6.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-6.png
new file mode 100644
index 0000000..b9367a8
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-6.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-7.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-7.png
new file mode 100644
index 0000000..3108e07
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-7.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-8.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-8.png
new file mode 100644
index 0000000..2f3838a
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-8.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-9.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-9.png
new file mode 100644
index 0000000..ebdb9be
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-theme-selector-9.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-1.png
new file mode 100644
index 0000000..6cc7c52
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-2.png
new file mode 100644
index 0000000..efbdd68
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-3.png
new file mode 100644
index 0000000..928a2ee
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-4.png
new file mode 100644
index 0000000..16ff796
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-5.png
new file mode 100644
index 0000000..f11b1e1
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-6.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-6.png
new file mode 100644
index 0000000..54a5228
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-6-video-options-6.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-audio-options-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-audio-options-1.png
new file mode 100644
index 0000000..e1101bd
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-audio-options-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-credits-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-credits-1.png
new file mode 100644
index 0000000..71ae041
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-credits-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-credits-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-credits-2.png
new file mode 100644
index 0000000..1c8d283
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-credits-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-1.png
new file mode 100644
index 0000000..2df47c5
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-2.png
new file mode 100644
index 0000000..77fd085
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-3.png
new file mode 100644
index 0000000..36916d7
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-4.png
new file mode 100644
index 0000000..263973e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-end-credits-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-game-won-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-game-won-1.png
new file mode 100644
index 0000000..3a5be94
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-game-won-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-options-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-options-1.png
new file mode 100644
index 0000000..a19f930
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-options-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-1.png
new file mode 100644
index 0000000..bb3024e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-2.png
new file mode 100644
index 0000000..360ce3d
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-3.png
new file mode 100644
index 0000000..8fcfa2a
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-4.png
new file mode 100644
index 0000000..fa6b50a
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-5.png
new file mode 100644
index 0000000..24a8dbb
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-input-remapping-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-1.png
new file mode 100644
index 0000000..02f8bc0
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-2.png
new file mode 100644
index 0000000..9243e20
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-lost-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-lost-1.png
new file mode 100644
index 0000000..2caca8d
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-lost-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-won-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-won-1.png
new file mode 100644
index 0000000..3bef366
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-level-won-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-1.png
new file mode 100644
index 0000000..83d2ce4
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-2.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-2.png
new file mode 100644
index 0000000..3f26c4e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-2.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-3.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-3.png
new file mode 100644
index 0000000..a4a4221
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-3.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-4.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-4.png
new file mode 100644
index 0000000..37714f5
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-4.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-5.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-5.png
new file mode 100644
index 0000000..b477e8e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-5.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-6.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-6.png
new file mode 100644
index 0000000..3717561
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-loading-screen-6.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-main-menu-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-main-menu-1.png
new file mode 100644
index 0000000..7edf067
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-main-menu-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-tutorial-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-tutorial-1.png
new file mode 100644
index 0000000..c4963d6
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-tutorial-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-video-options-1.png b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-video-options-1.png
new file mode 100644
index 0000000..65fe081
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/screenshot-7-video-options-1.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/setup-wizard-location.png b/evolve-die-repeat/addons/maaacks_game_template/media/setup-wizard-location.png
new file mode 100644
index 0000000..2640306
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/setup-wizard-location.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/setup-wizard-window.png b/evolve-die-repeat/addons/maaacks_game_template/media/setup-wizard-window.png
new file mode 100644
index 0000000..5471106
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/setup-wizard-window.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-a-darkness-like-gravity.png b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-a-darkness-like-gravity.png
new file mode 100644
index 0000000..880e5cb
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-a-darkness-like-gravity.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-baking-godium.png b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-baking-godium.png
new file mode 100644
index 0000000..a8788ae
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-baking-godium.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-heartfix-express.png b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-heartfix-express.png
new file mode 100644
index 0000000..7e5144e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-heartfix-express.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-rent-seek-kill.png b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-rent-seek-kill.png
new file mode 100644
index 0000000..9b67b6d
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-rent-seek-kill.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-spud-customs.png b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-spud-customs.png
new file mode 100644
index 0000000..59a2d7e
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/thumbnail-game-spud-customs.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/media/ui_sound_controller-icon-black-transparent-256x256.png b/evolve-die-repeat/addons/maaacks_game_template/media/ui_sound_controller-icon-black-transparent-256x256.png
new file mode 100644
index 0000000..30cbe58
Binary files /dev/null and b/evolve-die-repeat/addons/maaacks_game_template/media/ui_sound_controller-icon-black-transparent-256x256.png differ
diff --git a/evolve-die-repeat/addons/maaacks_game_template/plugin.cfg b/evolve-die-repeat/addons/maaacks_game_template/plugin.cfg
new file mode 100644
index 0000000..78eab18
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/plugin.cfg
@@ -0,0 +1,9 @@
+[plugin]
+
+name="Maaack's Game Template"
+description="Template with a main menu, options menus, pause menu, credits, scene loader, extra tools, and an example game scene.
+
+Created in collaboration with members of the Godot Wild Jam community."
+author="Marek Belski"
+version="1.4.6"
+script="plugin.gd"
diff --git a/evolve-die-repeat/addons/maaacks_game_template/plugin.gd b/evolve-die-repeat/addons/maaacks_game_template/plugin.gd
new file mode 100644
index 0000000..088e5a8
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/plugin.gd
@@ -0,0 +1,391 @@
+@tool
+class_name MaaacksGameTemplatePlugin
+extends EditorPlugin
+
+const PLUGIN_PATH = "res://addons/maaacks_game_template/"
+const PLUGIN_NAME = "Maaack's Game Template"
+const PROJECT_SETTINGS_PATH = "maaacks_game_template/"
+
+const APIClient = preload(PLUGIN_PATH + "utilities/api_client.gd")
+const DownloadAndExtract = preload(PLUGIN_PATH + "utilities/download_and_extract.gd")
+const CopyAndEdit = preload(PLUGIN_PATH + "installer/copy_and_edit_files.gd")
+
+const EXAMPLES_RELATIVE_PATH = "examples/"
+const MAIN_SCENE_RELATIVE_PATH = "scenes/opening/opening.tscn"
+const OVERRIDE_RELATIVE_PATH = "installer/override.cfg"
+const APP_CONFIG_RELATIVE_PATH = "base/nodes/autoloads/app_config/app_config.tscn"
+const SCENE_LOADER_RELATIVE_PATH = "base/nodes/autoloads/scene_loader/scene_loader.tscn"
+const THEMES_DIRECTORY_RELATIVE_PATH = "resources/themes"
+const WINDOW_OPEN_DELAY : float = 0.5
+const RUNNING_CHECK_DELAY : float = 0.25
+const OPEN_EDITOR_DELAY : float = 0.1
+const MAX_PHYSICS_FRAMES_FROM_START : int = 60
+const AVAILABLE_TRANSLATIONS : Array = ["en", "fr"]
+
+static var instance : MaaacksGameTemplatePlugin
+
+var selected_theme : String
+var update_plugin_tool_string : String
+
+static func get_plugin_name() -> String:
+ return PLUGIN_NAME
+
+static func get_settings_path() -> String:
+ return PROJECT_SETTINGS_PATH
+
+static func get_plugin_path() -> String:
+ return PLUGIN_PATH
+
+static func get_plugin_examples_path() -> String:
+ return get_plugin_path() + EXAMPLES_RELATIVE_PATH
+
+static func get_app_config_path() -> String:
+ return get_plugin_path() + APP_CONFIG_RELATIVE_PATH
+
+static func get_scene_loader_path() -> String:
+ return get_plugin_path() + SCENE_LOADER_RELATIVE_PATH
+
+static func get_copy_path() -> String:
+ var copy_path = ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "copy_path", get_plugin_examples_path())
+ if not copy_path.ends_with("/"):
+ copy_path += "/"
+ return copy_path
+
+func _on_theme_selected(theme_resource_path: String) -> void:
+ selected_theme = theme_resource_path
+
+func _update_gui_theme() -> void:
+ if selected_theme.is_empty(): return
+ ProjectSettings.set_setting("gui/theme/custom", selected_theme)
+ ProjectSettings.save()
+
+func _on_visibility_changed_to_hidden(dialog_window : Window) -> void:
+ if dialog_window and dialog_window.is_inside_tree() and not dialog_window.visible:
+ dialog_window.queue_free()
+
+func open_theme_selection_dialog(target_path : String) -> void:
+ selected_theme = ""
+ var theme_selection_scene : PackedScene = load(get_plugin_path() + "installer/theme_selection_dialog.tscn")
+ var theme_selection_instance : ConfirmationDialog = theme_selection_scene.instantiate()
+ theme_selection_instance.confirmed.connect(_update_gui_theme)
+ theme_selection_instance.theme_selected.connect(_on_theme_selected)
+ theme_selection_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(theme_selection_instance))
+ add_child(theme_selection_instance)
+ var theme_directores : Array[String]
+ theme_directores.append(target_path + THEMES_DIRECTORY_RELATIVE_PATH)
+ theme_selection_instance.theme_directories = theme_directores
+
+func open_setup_complete_dialog(_target_path : String) -> void:
+ var setup_complete_scene : PackedScene = load(get_plugin_path() + "installer/setup_complete_dialog.tscn")
+ var setup_complete_instance : AcceptDialog = setup_complete_scene.instantiate()
+ setup_complete_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(setup_complete_instance))
+ add_child(setup_complete_instance)
+
+func _delayed_call_with_path(callable : Callable, target_path : String) -> void:
+ var timer: Timer = Timer.new()
+ var timer_callable := func():
+ timer.stop()
+ callable.call(target_path)
+ timer.queue_free()
+ timer.timeout.connect(timer_callable)
+ add_child(timer)
+ timer.start(WINDOW_OPEN_DELAY)
+
+func _delayed_open_setup_complete_dialog(target_path : String) -> void:
+ _delayed_call_with_path(open_setup_complete_dialog, target_path)
+
+func _update_main_scene(target_path : String, main_scene_path : String) -> void:
+ ProjectSettings.set_setting("application/run/main_scene", main_scene_path)
+ ProjectSettings.save()
+ _delayed_open_setup_complete_dialog(target_path)
+
+func is_main_scene_set(target_path : String = get_copy_path()) -> bool:
+ var current_main_scene_path = ProjectSettings.get_setting("application/run/main_scene", "")
+ var new_main_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH
+ return current_main_scene_path == new_main_scene_path
+
+func _check_main_scene_needs_updating(target_path : String) -> void:
+ if not is_main_scene_set(target_path):
+ open_main_scene_confirmation_dialog(target_path)
+ return
+ _delayed_open_setup_complete_dialog(target_path)
+
+func open_main_scene_confirmation_dialog(target_path : String) -> void:
+ var main_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/main_scene_confirmation_dialog.tscn")
+ var main_confirmation_instance : ConfirmationDialog = main_confirmation_scene.instantiate()
+ var new_main_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH
+ if main_confirmation_instance.has_method(&"set_main_scene_text"):
+ main_confirmation_instance.set_main_scene_text(new_main_scene_path)
+ main_confirmation_instance.confirmed.connect(_update_main_scene.bind(target_path, new_main_scene_path))
+ main_confirmation_instance.canceled.connect(_delayed_open_setup_complete_dialog.bind(target_path))
+ main_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(main_confirmation_instance))
+ add_child(main_confirmation_instance)
+
+func _open_play_opening_confirmation_dialog(target_path : String) -> void:
+ var play_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/play_opening_confirmation_dialog.tscn")
+ var play_confirmation_instance : ConfirmationDialog = play_confirmation_scene.instantiate()
+ play_confirmation_instance.confirmed.connect(_run_opening_scene.bind(target_path))
+ play_confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(target_path))
+ play_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(play_confirmation_instance))
+ add_child(play_confirmation_instance)
+
+func _open_delete_examples_confirmation_dialog(target_path : String) -> void:
+ var delete_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/delete_examples_confirmation_dialog.tscn")
+ var delete_confirmation_instance : ConfirmationDialog = delete_confirmation_scene.instantiate()
+ delete_confirmation_instance.confirmed.connect(_delete_source_examples_directory.bind(target_path))
+ delete_confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(target_path))
+ delete_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(delete_confirmation_instance))
+ add_child(delete_confirmation_instance)
+
+func open_delete_examples_short_confirmation_dialog() -> void:
+ var delete_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/delete_examples_short_confirmation_dialog.tscn")
+ var delete_confirmation_instance : ConfirmationDialog = delete_confirmation_scene.instantiate()
+ delete_confirmation_instance.confirmed.connect(_delete_source_examples_directory)
+ delete_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(delete_confirmation_instance))
+ add_child(delete_confirmation_instance)
+
+func _run_opening_scene(target_path : String) -> void:
+ var opening_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH
+ EditorInterface.play_custom_scene(opening_scene_path)
+ var timer: Timer = Timer.new()
+ var callable := func() -> void:
+ if EditorInterface.is_playing_scene(): return
+ timer.stop()
+ _delayed_call_with_path(_open_delete_examples_confirmation_dialog, target_path)
+ timer.queue_free()
+ timer.timeout.connect(callable)
+ add_child(timer)
+ timer.start(RUNNING_CHECK_DELAY)
+
+func _delete_directory_recursive(dir_path : String) -> void:
+ if not dir_path.ends_with("/"):
+ dir_path += "/"
+ var dir = DirAccess.open(dir_path)
+ if dir:
+ dir.list_dir_begin()
+ var file_name = dir.get_next()
+ var error : Error
+ while file_name != "" and error == 0:
+ var relative_path = dir_path.trim_prefix(get_plugin_examples_path())
+ var full_file_path = dir_path + file_name
+ if dir.current_is_dir():
+ _delete_directory_recursive(full_file_path)
+ else:
+ error = dir.remove(file_name)
+ file_name = dir.get_next()
+ if error:
+ push_error("plugin error - deleting path: %s" % error)
+ else:
+ push_error("plugin error - accessing path: %s" % dir)
+ dir.remove(dir_path)
+
+func _delete_source_examples_directory(target_path : String = "") -> void:
+ var examples_path = get_plugin_examples_path()
+ var dir := DirAccess.open("res://")
+ if dir.dir_exists(examples_path):
+ _delete_directory_recursive(examples_path)
+ EditorInterface.get_resource_filesystem().scan()
+ if not target_path.is_empty():
+ _check_main_scene_needs_updating(target_path)
+
+func _raw_copy_file_path(file_path : String, destination_path : String) -> Error:
+ var dir := DirAccess.open("res://")
+ var error := dir.copy(file_path, destination_path)
+ return error
+
+func _copy_override_file() -> void:
+ var override_path : String = get_plugin_path() + OVERRIDE_RELATIVE_PATH
+ _raw_copy_file_path(override_path, "res://"+override_path.get_file())
+
+func _update_app_config_paths(target_path : String) -> void:
+ var file_path : String = get_app_config_path()
+ var file_text : String = FileAccess.get_file_as_string(file_path)
+ var prefixes : Array[String] = [
+ "main_menu_scene_path",
+ "game_scene_path",
+ "ending_scene_path",
+ ]
+ for prefix in prefixes:
+ prefix += " = \""
+ var target_string = prefix + get_plugin_examples_path()
+ var replacing_string = prefix + target_path
+ file_text = file_text.replace(target_string, replacing_string)
+ var file = FileAccess.open(file_path, FileAccess.WRITE)
+ file.store_string(file_text)
+ file.close()
+
+func _update_scene_loader_path(target_path : String) -> void:
+ var file_path : String = get_scene_loader_path()
+ var file_text : String = FileAccess.get_file_as_string(file_path)
+ var prefix : String = "loading_screen_path = \""
+ var target_string = prefix + get_plugin_examples_path()
+ var replacing_string = prefix + target_path
+ file_text = file_text.replace(target_string, replacing_string)
+ var file = FileAccess.open(file_path, FileAccess.WRITE)
+ file.store_string(file_text)
+ file.close()
+
+func _add_translations() -> void:
+ var dir := DirAccess.open("res://")
+ var translations : PackedStringArray = ProjectSettings.get_setting("internationalization/locale/translations", [])
+ for available_translation in AVAILABLE_TRANSLATIONS:
+ var translation_path = get_plugin_path() + ("base/translations/menus_translations.%s.translation" % available_translation)
+ if dir.file_exists(translation_path) and translation_path not in translations:
+ translations.append(translation_path)
+ ProjectSettings.set_setting("internationalization/locale/translations", translations)
+
+func _is_app_config_path_updated(target_path) -> bool:
+ var file_text : String = FileAccess.get_file_as_string(get_app_config_path())
+ var target_string = "main_menu_scene_path = \"" + get_plugin_examples_path()
+ return !file_text.contains(target_string)
+
+func _is_scene_loader_path_updated(target_path) -> bool:
+ var file_text : String = FileAccess.get_file_as_string(get_scene_loader_path())
+ var target_string = "loading_screen_path = \"" + get_plugin_examples_path()
+ return !file_text.contains(target_string)
+
+func are_autoload_paths_updated() -> bool:
+ var copy_path := get_copy_path()
+ if copy_path == get_plugin_examples_path(): return false
+ return _is_app_config_path_updated(copy_path) and _is_scene_loader_path_updated(copy_path)
+
+func update_autoload_paths(target_path : String) -> void:
+ _update_app_config_paths(target_path)
+ _update_scene_loader_path(target_path)
+
+func _on_completed_copy_to_directory(target_path : String) -> void:
+ ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "copy_path", target_path)
+ ProjectSettings.save()
+ update_autoload_paths(target_path)
+ _copy_override_file()
+ _open_play_opening_confirmation_dialog(target_path)
+
+func open_input_icons_dialog() -> void:
+ var input_icons_scene : PackedScene = load(get_plugin_path() + "installer/kenney_input_prompts_installer.tscn")
+ var input_icons_instance = input_icons_scene.instantiate()
+ input_icons_instance.copy_dir_path = get_copy_path()
+ add_child(input_icons_instance)
+
+func open_copy_and_edit_dialog() -> void:
+ var copy_and_edit_scene : PackedScene = load(get_plugin_path() + "installer/copy_and_edit_files.tscn")
+ var copy_and_edit_instance : CopyAndEdit = copy_and_edit_scene.instantiate()
+ copy_and_edit_instance.completed.connect(_on_completed_copy_to_directory)
+ copy_and_edit_instance.canceled.connect(_check_main_scene_needs_updating.bind(get_copy_path()))
+ add_child(copy_and_edit_instance)
+
+func _open_confirmation_dialog() -> void:
+ var confirmation_scene : PackedScene = load(get_plugin_path() + "installer/copy_confirmation_dialog.tscn")
+ var confirmation_instance : ConfirmationDialog = confirmation_scene.instantiate()
+ confirmation_instance.confirmed.connect(open_copy_and_edit_dialog)
+ confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(get_copy_path()))
+ confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(confirmation_instance))
+ add_child(confirmation_instance)
+
+func _open_check_plugin_version() -> void:
+ if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_update_check"):
+ if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_update_check"):
+ return
+ else:
+ ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_update_check", false)
+ ProjectSettings.save()
+ var check_version_scene : PackedScene = load(get_plugin_path() + "installer/check_plugin_version.tscn")
+ var check_version_instance : Node = check_version_scene.instantiate()
+ check_version_instance.auto_start = true
+ check_version_instance.new_version_detected.connect(_add_update_plugin_tool_option)
+ add_child(check_version_instance)
+
+func open_update_plugin() -> void:
+ var update_plugin_scene : PackedScene = load(get_plugin_path() + "installer/update_plugin.tscn")
+ var update_plugin_instance : Node = update_plugin_scene.instantiate()
+ update_plugin_instance.auto_start = true
+ update_plugin_instance.update_completed.connect(_remove_update_plugin_tool_option)
+ add_child(update_plugin_instance)
+
+func open_setup_wizard() -> void:
+ var setup_wizard_scene : PackedScene = load(get_plugin_path() + "installer/setup_wizard.tscn")
+ var setup_wizard_instance : Node = setup_wizard_scene.instantiate()
+ add_child(setup_wizard_instance)
+
+func _add_update_plugin_tool_option(new_version : String) -> void:
+ update_plugin_tool_string = "Update %s to v%s..." % [get_plugin_name(), new_version]
+ add_tool_menu_item(update_plugin_tool_string, open_update_plugin)
+
+func _remove_update_plugin_tool_option() -> void:
+ if update_plugin_tool_string.is_empty(): return
+ remove_tool_menu_item(update_plugin_tool_string)
+ update_plugin_tool_string = ""
+
+func _show_plugin_dialogues() -> void:
+ if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_install_wizard") :
+ if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_install_wizard") :
+ return
+ _open_confirmation_dialog()
+ ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_install_wizard", true)
+ ProjectSettings.save()
+
+func _resave_if_recently_opened() -> void:
+ if Engine.get_physics_frames() < MAX_PHYSICS_FRAMES_FROM_START:
+ var timer: Timer = Timer.new()
+ var callable := func():
+ if Engine.get_frames_per_second() >= 10:
+ timer.stop()
+ EditorInterface.save_scene()
+ timer.queue_free()
+ timer.timeout.connect(callable)
+ add_child(timer)
+ timer.start(OPEN_EDITOR_DELAY)
+
+func _add_audio_bus(bus_name : String) -> void:
+ var has_bus_name := false
+ for bus_idx in range(AudioServer.bus_count):
+ var existing_bus_name := AudioServer.get_bus_name(bus_idx)
+ if existing_bus_name == bus_name:
+ has_bus_name = true
+ break
+ if not has_bus_name:
+ AudioServer.add_bus()
+ var new_bus_idx := AudioServer.bus_count - 1
+ AudioServer.set_bus_name(new_bus_idx, bus_name)
+ AudioServer.set_bus_send(new_bus_idx, &"Master")
+ ProjectSettings.save()
+
+func _install_audio_busses() -> void:
+ if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_install_audio_busses"):
+ if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_install_audio_busses") :
+ return
+ _add_audio_bus("Music")
+ _add_audio_bus("SFX")
+ ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_install_audio_busses", true)
+ ProjectSettings.save()
+
+func _add_tool_options() -> void:
+ add_tool_menu_item("Run " + get_plugin_name() + " Setup...", open_setup_wizard)
+ _open_check_plugin_version()
+
+func _remove_tool_options() -> void:
+ remove_tool_menu_item("Run " + get_plugin_name() + " Setup...")
+ _remove_update_plugin_tool_option()
+
+func _enable_plugin():
+ add_autoload_singleton("AppConfig", get_app_config_path())
+ add_autoload_singleton("SceneLoader", get_scene_loader_path())
+ add_autoload_singleton("ProjectMusicController", get_plugin_path() + "base/nodes/autoloads/music_controller/project_music_controller.tscn")
+ add_autoload_singleton("ProjectUISoundController", get_plugin_path() + "base/nodes/autoloads/ui_sound_controller/project_ui_sound_controller.tscn")
+
+func _disable_plugin():
+ remove_autoload_singleton("AppConfig")
+ remove_autoload_singleton("SceneLoader")
+ remove_autoload_singleton("ProjectMusicController")
+ remove_autoload_singleton("ProjectUISoundController")
+
+func _enter_tree() -> void:
+ _install_audio_busses()
+ _add_tool_options()
+ _add_translations()
+ _show_plugin_dialogues()
+ _resave_if_recently_opened()
+ instance = self
+
+func _exit_tree() -> void:
+ _remove_tool_options()
+ instance = null
diff --git a/evolve-die-repeat/addons/maaacks_game_template/plugin.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/plugin.gd.uid
new file mode 100644
index 0000000..2b07b65
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/plugin.gd.uid
@@ -0,0 +1 @@
+uid://bndaaa5when2r
diff --git a/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.gd b/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.gd
new file mode 100644
index 0000000..a15394d
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.gd
@@ -0,0 +1,156 @@
+@tool
+extends Node
+## Node for sending a requesti to an API endpoint for a JSON-encoded response.
+
+signal response_received(response_body)
+signal request_failed(error)
+
+const RESULT_CANT_CONNECT = "Failed to connect"
+const RESULT_CANT_RESOLVE = "Failed to resolve"
+const RESULT_CONNECTION_ERROR = "Connection error"
+const RESULT_TIMEOUT = "Connection timeout"
+const RESULT_SERVER_ERROR = "Server error"
+const REQUEST_FAILED = "Error in the request"
+const REQUEST_TIMEOUT = "Request timed out on the client side"
+const URL_NOT_SET = "URL parameter is not set"
+const PARSE_FAILED = "Parsing failed"
+
+## Location of the API endpoint.
+@export var api_url : String
+## HTTP request method to use. Typically GET or POST.
+@export var request_method : HTTPClient.Method = HTTPClient.METHOD_POST
+@export_group("Advanced")
+## Location of an API key file, if authorization is required by the endpoint.
+@export_file("*.txt") var api_key_file : String
+## Time in seconds before the request fails due to timeout.
+@export var request_timeout : float = 0.0
+## If true, test sending a request.
+## Replace with @export_tool_button for Godot 4.4+
+@export var _send_request_action : bool = false :
+ set(value):
+ if value and Engine.is_editor_hint():
+ request()
+# For Godot 4.4+
+# @export_tool_button("Send Request") var _send_request_action = request
+
+
+@onready var _http_request : HTTPRequest = $HTTPRequest
+@onready var _timeout_timer : Timer= $TimeoutTimer
+
+## State flag for whether the connection has timed out on the client-side.
+var timed_out : bool = false
+
+func get_http_request() -> HTTPRequest:
+ return _http_request
+
+func get_api_key() -> String:
+ if api_key_file.is_empty():
+ return ""
+ var file := FileAccess.open(api_key_file, FileAccess.READ)
+ var error := FileAccess.get_open_error()
+ if error != OK:
+ push_error("API Key reading error: %d" % error)
+ return ""
+ var content = file.get_as_text()
+ file.close()
+ return content
+
+func get_api_url() -> String:
+ return api_url
+
+func get_api_method() -> int:
+ return request_method
+
+func mock_empty_body() -> String:
+ var form : Dictionary = {}
+ return JSON.stringify(form)
+
+func mock_request(body : String):
+ await(get_tree().create_timer(10.0).timeout)
+ _on_request_completed(HTTPRequest.RESULT_SUCCESS, "200", [], body)
+
+func request(body : String = "", request_headers : Array = []) -> void:
+ var local_http_request : HTTPRequest = get_http_request()
+ var key : String = get_api_key()
+ var url : String = get_api_url()
+ var method : int = get_api_method()
+ if url.is_empty():
+ request_failed.emit(URL_NOT_SET)
+ push_error(URL_NOT_SET)
+ return
+ request_headers.append("Content-Type: application/json")
+ if key:
+ request_headers.append("x-api-key: %s" % key)
+ if request_timeout > 0.0:
+ local_http_request.timeout = request_timeout
+ var error = local_http_request.request(url, request_headers, method, body)
+ if error != OK:
+ request_failed.emit(REQUEST_FAILED)
+ push_error("HTTP Request error: %d" % error)
+ return
+ if request_timeout > 0.0:
+ _timeout_timer.start(request_timeout + 1.0)
+
+func request_raw(data : PackedByteArray = [], request_headers : Array = []) -> void:
+ var local_http_request : HTTPRequest = get_http_request()
+ var key : String = get_api_key()
+ var url : String = get_api_url()
+ var method : int = get_api_method()
+ if url.is_empty():
+ request_failed.emit(URL_NOT_SET)
+ push_error(URL_NOT_SET)
+ return
+ request_headers.append("Content-Type: application/json")
+ if key:
+ request_headers.append("x-api-key: %s" % key)
+ if request_timeout > 0.0:
+ local_http_request.timeout = request_timeout
+ var error = local_http_request.request_raw(url, request_headers, method, data)
+ if error != OK:
+ request_failed.emit(REQUEST_FAILED)
+ push_error("HTTP Request error: %d" % error)
+ return
+ if request_timeout > 0.0:
+ _timeout_timer.start(request_timeout + 1.0)
+
+func _on_request_completed(result, response_code, headers, body) -> void:
+ # If already timed out on client-side, then return.
+ if timed_out: return
+ _timeout_timer.stop()
+ if result == HTTPRequest.RESULT_SUCCESS:
+ var body_string : String
+ if body is PackedByteArray:
+ body_string = body.get_string_from_utf8()
+ elif body is String:
+ body_string = body
+ var json := JSON.new()
+ var error = json.parse(body_string)
+ if error != OK:
+ request_failed.emit(PARSE_FAILED)
+ push_error("Parse error: %d" % error)
+ return
+ var parsed_data = json.data
+ response_received.emit(json.data)
+ else:
+ var error_message : String
+ match(result):
+ HTTPRequest.RESULT_CANT_CONNECT:
+ error_message = RESULT_CANT_CONNECT
+ HTTPRequest.RESULT_CANT_RESOLVE:
+ error_message = RESULT_CANT_RESOLVE
+ HTTPRequest.RESULT_CONNECTION_ERROR:
+ error_message = RESULT_CONNECTION_ERROR
+ HTTPRequest.RESULT_TIMEOUT:
+ error_message = RESULT_TIMEOUT
+ _:
+ error_message = RESULT_SERVER_ERROR
+ request_failed.emit(error_message)
+ push_error("HTTP Result error: %d" % result)
+
+func _on_http_request_request_completed(result, response_code, headers, body) -> void:
+ _on_request_completed(result, response_code, headers, body)
+
+func _on_timeout_timer_timeout() -> void:
+ timed_out = true
+ request_failed.emit(REQUEST_TIMEOUT)
+ push_warning(REQUEST_TIMEOUT)
diff --git a/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.gd.uid
new file mode 100644
index 0000000..335a8e4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.gd.uid
@@ -0,0 +1 @@
+uid://s0j82xowl675
diff --git a/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.tscn b/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.tscn
new file mode 100644
index 0000000..dc54e01
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/utilities/api_client.tscn
@@ -0,0 +1,13 @@
+[gd_scene format=3 uid="uid://drhhakm62vjsy"]
+
+[ext_resource type="Script" uid="uid://s0j82xowl675" path="res://addons/maaacks_game_template/utilities/api_client.gd" id="1_c5ofg"]
+
+[node name="APIClient" type="Node" unique_id=314090132]
+script = ExtResource("1_c5ofg")
+
+[node name="HTTPRequest" type="HTTPRequest" parent="." unique_id=1687147368]
+
+[node name="TimeoutTimer" type="Timer" parent="." unique_id=880426190]
+
+[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
+[connection signal="timeout" from="TimeoutTimer" to="." method="_on_timeout_timer_timeout"]
diff --git a/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.gd b/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.gd
new file mode 100644
index 0000000..49985d4
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.gd
@@ -0,0 +1,284 @@
+@tool
+## Utility node for downloading and unzipping a file from a URL to an extraction destination.
+extends Node
+
+## Sent when the run has completed.
+signal run_completed
+## Sent when a response is received from the server.
+signal response_received(response_body)
+## Sent when the run has failed or exited early for any reason.
+signal run_failed(error : String)
+## Sent when the zip file has finished saving.
+signal zip_saved
+
+const TEMPORARY_ZIP_PATH = "res://temp.zip"
+const RESULT_CANT_CONNECT = "Failed to connect"
+const RESULT_CANT_RESOLVE = "Failed to resolve"
+const RESULT_CONNECTION_ERROR = "Connection error"
+const RESULT_TIMEOUT = "Connection timeout"
+const RESULT_SERVER_ERROR = "Server error"
+const REQUEST_FAILED = "Error in the request"
+const REQUEST_TIMEOUT = "Request timed out on the client side"
+const DOWNLOAD_IN_PROGRESS = "Download already in progress"
+const EXTRACT_IN_PROGRESS = "Extract already in progress"
+const DELETE_IN_PROGRESS = "Delete already in progress"
+const FAILED_TO_SAVE_ZIP_FILE = "Failed to save the zip file"
+const FAILED_TO_MAKE_EXTRACT_DIR = "Failed to make extract directory"
+const FAILED_TO_READ_ZIP_FILE = "Failed to read the zip file"
+const DOWNLOADED_ZIP_FILE_DOESNT_EXIST = "The downloaded ZIP file doesn't exist"
+const URL_NOT_SET = "URL parameter is not set"
+
+enum DownloadAndExtractStage{
+ NONE,
+ DOWNLOAD,
+ SAVE,
+ EXTRACT,
+ DELETE,
+}
+
+## Location of the zip file to be downloaded.
+@export var zip_url : String
+## Path where the zipped files are to be extracted.
+@export_dir var extract_path : String
+@export_group("Advanced")
+## If not empty, zipped file paths that do not contain a match to the string will be ignored.
+@export var path_match_string : String = ""
+## Assuming zip file contains a single base directory, the flag copies all of the contents,
+## as if they were at the base of the zip file. It never makes the base directory locally.
+@export var skip_base_zip_dir : bool = false
+## Forces a download and extraction even if the files already exist.
+@export var force : bool = false
+## Path where the zip file will be stored.
+@export var zip_file_path : String = TEMPORARY_ZIP_PATH
+## If true, delete a downloaded zip file after the contents are extracted.
+@export var delete_zip_file : bool = true
+## Ratio of processing time that should be spent on extracting files.
+@export_range(0.0, 1.0) var process_time_ratio : float = 0.75
+## Seconds of delay added between saving the zip file and extracting it.
+@export_range(0.0, 3.0) var extraction_delay : float = 0.25
+## Duration to wait before the request times out.
+@export var request_timeout : float = 0.0
+@export var _start_run_action : bool = false :
+ set(value):
+ if value and Engine.is_editor_hint():
+ run()
+# For Godot 4.4
+# @export_tool_button("Download & Extract") var _start_run_action = run
+
+
+@onready var _http_request : HTTPRequest = $HTTPRequest
+@onready var _timeout_timer : Timer= $TimeoutTimer
+
+## State flag for whether the connection has timed out on the client-side.
+var timed_out : bool = false
+## Current stage of the download and extract process.
+var stage : DownloadAndExtractStage = DownloadAndExtractStage.NONE
+var zip_reader : ZIPReader = ZIPReader.new()
+var zipped_file_paths : PackedStringArray = []
+var extracted_file_paths : Array[String] = []
+var skipped_file_paths : Array[String] = []
+var downloaded_zip_file : bool = false
+var base_zip_path : String = ""
+var _save_progress : float = 0.0
+
+func get_http_request() -> HTTPRequest:
+ return _http_request
+
+func get_zip_url() -> String:
+ return zip_url
+
+func _zip_exists() -> bool:
+ return FileAccess.file_exists(zip_file_path)
+
+func get_request_method() -> int:
+ return HTTPClient.METHOD_GET
+
+## Sends the request to download the target zip file, and then extracts the contents.
+func run(request_headers : Array = []) -> void:
+ if stage == DownloadAndExtractStage.DOWNLOAD:
+ run_failed.emit(DOWNLOAD_IN_PROGRESS)
+ push_warning(DOWNLOAD_IN_PROGRESS)
+ return
+ if _zip_exists() and not force:
+ _extract_files.call_deferred()
+ return
+ var local_http_request : HTTPRequest = get_http_request()
+ var url : String = get_zip_url()
+ var method : int = get_request_method()
+ if url.is_empty():
+ run_failed.emit(URL_NOT_SET)
+ push_error(URL_NOT_SET)
+ return
+ if request_timeout > 0.0:
+ local_http_request.timeout = request_timeout
+ var error = local_http_request.request(url, request_headers, method)
+ if error != OK:
+ run_failed.emit(REQUEST_FAILED)
+ push_error("HTTP Request error: %d" % error)
+ return
+ if request_timeout > 0.0:
+ _timeout_timer.start(request_timeout + 1.0)
+ stage = DownloadAndExtractStage.DOWNLOAD
+
+func _delete_zip_file() -> void:
+ if not delete_zip_file or not downloaded_zip_file: return
+ if stage == DownloadAndExtractStage.DELETE:
+ run_failed.emit(DELETE_IN_PROGRESS)
+ push_warning(DELETE_IN_PROGRESS)
+ return
+ stage = DownloadAndExtractStage.DELETE
+ DirAccess.remove_absolute(zip_file_path)
+ downloaded_zip_file = false
+
+func _save_zip_file(body : PackedByteArray) -> void:
+ stage = DownloadAndExtractStage.SAVE
+ var file = FileAccess.open(zip_file_path, FileAccess.WRITE)
+ if not file:
+ run_failed.emit(FAILED_TO_SAVE_ZIP_FILE)
+ push_error(FAILED_TO_SAVE_ZIP_FILE)
+ return
+ file.store_buffer(body)
+ file.close()
+ downloaded_zip_file = true
+ zip_saved.emit()
+
+func extract_path_exists() -> bool:
+ return DirAccess.dir_exists_absolute(extract_path)
+
+func _make_extract_path() -> void:
+ var err := DirAccess.make_dir_recursive_absolute(extract_path)
+ if err != OK:
+ run_failed.emit(FAILED_TO_MAKE_EXTRACT_DIR)
+ push_error(FAILED_TO_MAKE_EXTRACT_DIR)
+
+func _extract_files() -> void:
+ if stage == DownloadAndExtractStage.EXTRACT:
+ run_failed.emit(EXTRACT_IN_PROGRESS)
+ push_warning(EXTRACT_IN_PROGRESS)
+ return
+ stage = DownloadAndExtractStage.EXTRACT
+ if not _zip_exists():
+ run_failed.emit(DOWNLOADED_ZIP_FILE_DOESNT_EXIST)
+ push_error(DOWNLOADED_ZIP_FILE_DOESNT_EXIST)
+ return
+ if not extract_path_exists(): _make_extract_path()
+ var error = zip_reader.open(zip_file_path)
+ if error != OK:
+ run_failed.emit(FAILED_TO_READ_ZIP_FILE)
+ push_error("ZIP Reader error: %d" % error)
+ return
+ zipped_file_paths = zip_reader.get_files()
+ if skip_base_zip_dir:
+ base_zip_path = zipped_file_paths[0]
+ if not base_zip_path.ends_with("/"):
+ push_warning("Skipping extracting base path, but it is not a directory.")
+ zipped_file_paths.remove_at(0)
+
+func _on_request_completed(result, response_code, headers, body) -> void:
+ # If already timed out on client-side, then return.
+ if timed_out: return
+ _timeout_timer.stop()
+ if _zip_exists(): _delete_zip_file()
+ if result == HTTPRequest.RESULT_SUCCESS:
+ if body is PackedByteArray:
+ response_received.emit(body)
+ _save_zip_file(body)
+ _save_progress = 0.0
+ var tween = create_tween()
+ tween.tween_property(self, "_save_progress", 1.0, extraction_delay)
+ await tween.finished
+ _extract_files.call_deferred()
+ else:
+ var error_message : String
+ match(result):
+ HTTPRequest.RESULT_CANT_CONNECT:
+ error_message = RESULT_CANT_CONNECT
+ HTTPRequest.RESULT_CANT_RESOLVE:
+ error_message = RESULT_CANT_RESOLVE
+ HTTPRequest.RESULT_CONNECTION_ERROR:
+ error_message = RESULT_CONNECTION_ERROR
+ HTTPRequest.RESULT_TIMEOUT:
+ error_message = RESULT_TIMEOUT
+ _:
+ error_message = RESULT_SERVER_ERROR
+ run_failed.emit(error_message)
+ push_error("HTTP Result error: %d" % result)
+
+func _on_http_request_request_completed(result, response_code, headers, body) -> void:
+ _on_request_completed(result, response_code, headers, body)
+
+func _on_timeout_timer_timeout() -> void:
+ timed_out = true
+ run_failed.emit(REQUEST_TIMEOUT)
+ push_warning(REQUEST_TIMEOUT)
+
+func get_progress() -> float:
+ if stage == DownloadAndExtractStage.DOWNLOAD:
+ return get_download_progress()
+ elif stage == DownloadAndExtractStage.SAVE:
+ return get_save_progress()
+ elif stage == DownloadAndExtractStage.EXTRACT:
+ return get_extraction_progress()
+ return 0.0
+
+func get_save_progress() -> float:
+ return _save_progress
+
+func get_extraction_progress() -> float:
+ if zipped_file_paths.size() == 0:
+ return 0.0
+ return float(extracted_file_paths.size()) / float(zipped_file_paths.size())
+
+func get_download_progress() -> float:
+ var body_size := _http_request.get_body_size()
+ if body_size < 1: return 0.0
+ return float(_http_request.get_downloaded_bytes()) / float(body_size)
+
+func _zipped_files_remaining() -> int:
+ return zipped_file_paths.size() - (extracted_file_paths.size() + skipped_file_paths.size())
+
+func _extract_next_zipped_file() -> void:
+ var path_index = extracted_file_paths.size() + skipped_file_paths.size()
+ var zipped_file_path := zipped_file_paths[path_index]
+ if path_match_string and not zipped_file_path.contains(path_match_string):
+ skipped_file_paths.append(zipped_file_path)
+ return
+ var extract_path_dir := extract_path
+ if not extract_path_dir.ends_with("/"):
+ extract_path_dir += "/"
+ var full_path := extract_path_dir
+ if skip_base_zip_dir:
+ full_path += zipped_file_path.replace(base_zip_path, "")
+ else:
+ full_path += zipped_file_path
+ if full_path.ends_with("/"):
+ if not DirAccess.dir_exists_absolute(full_path):
+ DirAccess.make_dir_recursive_absolute(full_path)
+ else:
+ if not FileAccess.file_exists(full_path) or force:
+ var file_access := FileAccess.open(full_path, FileAccess.WRITE)
+ if file_access == null:
+ skipped_file_paths.append(zipped_file_path)
+ push_error("Failed to open file: %s" % full_path)
+ return
+ var file_contents = zip_reader.read_file(zipped_file_path)
+ file_access.store_buffer(file_contents)
+ file_access.close()
+ extracted_file_paths.append(full_path)
+
+func _finish_extraction() -> void:
+ zip_reader.close()
+ _delete_zip_file()
+ stage = DownloadAndExtractStage.NONE
+ run_completed.emit()
+
+func _process(delta : float) -> void:
+ if stage == DownloadAndExtractStage.EXTRACT:
+ var frame_start_time : float = Time.get_unix_time_from_system()
+ var frame_time : float = 0.0
+ while (frame_time < delta * process_time_ratio):
+ if _zipped_files_remaining() == 0:
+ _finish_extraction()
+ break
+ _extract_next_zipped_file()
+ frame_time = Time.get_unix_time_from_system() - frame_start_time
diff --git a/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.gd.uid b/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.gd.uid
new file mode 100644
index 0000000..d6b5bda
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.gd.uid
@@ -0,0 +1 @@
+uid://bqu3bc0tttrfk
diff --git a/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.tscn b/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.tscn
new file mode 100644
index 0000000..7d91ec9
--- /dev/null
+++ b/evolve-die-repeat/addons/maaacks_game_template/utilities/download_and_extract.tscn
@@ -0,0 +1,14 @@
+[gd_scene format=3 uid="uid://dlkmofxhavh10"]
+
+[ext_resource type="Script" uid="uid://bqu3bc0tttrfk" path="res://addons/maaacks_game_template/utilities/download_and_extract.gd" id="1_1few7"]
+
+[node name="DownloadAndExtract" type="Node" unique_id=870633091]
+script = ExtResource("1_1few7")
+
+[node name="HTTPRequest" type="HTTPRequest" parent="." unique_id=367121129]
+
+[node name="TimeoutTimer" type="Timer" parent="." unique_id=939371402]
+one_shot = true
+
+[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
+[connection signal="timeout" from="TimeoutTimer" to="." method="_on_timeout_timer_timeout"]
diff --git a/evolve-die-repeat/assets/git_logo/Git-Logo-2Color.png b/evolve-die-repeat/assets/git_logo/Git-Logo-2Color.png
new file mode 100644
index 0000000..18c5b29
Binary files /dev/null and b/evolve-die-repeat/assets/git_logo/Git-Logo-2Color.png differ
diff --git a/evolve-die-repeat/assets/git_logo/Git-Logo-2Color.png.import b/evolve-die-repeat/assets/git_logo/Git-Logo-2Color.png.import
new file mode 100644
index 0000000..7240b01
--- /dev/null
+++ b/evolve-die-repeat/assets/git_logo/Git-Logo-2Color.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b2pkmysruk0qd"
+path="res://.godot/imported/Git-Logo-2Color.png-73dc9fbbc7cfea42d3822db2a14ed5bd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/git_logo/Git-Logo-2Color.png"
+dest_files=["res://.godot/imported/Git-Logo-2Color.png-73dc9fbbc7cfea42d3822db2a14ed5bd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/assets/git_logo/LICENSE.txt b/evolve-die-repeat/assets/git_logo/LICENSE.txt
new file mode 100644
index 0000000..2d17b1d
--- /dev/null
+++ b/evolve-die-repeat/assets/git_logo/LICENSE.txt
@@ -0,0 +1,6 @@
+Git Logo
+Copyright (c) Jason Long
+
+This work is licensed under the Creative Commons Attribution 3.0 Unported
+license (CC BY 3.0): https://creativecommons.org/licenses/by/3.0/
+
diff --git a/evolve-die-repeat/assets/godot_engine_logo/LICENSE.txt b/evolve-die-repeat/assets/godot_engine_logo/LICENSE.txt
new file mode 100644
index 0000000..a081c9e
--- /dev/null
+++ b/evolve-die-repeat/assets/godot_engine_logo/LICENSE.txt
@@ -0,0 +1,5 @@
+Godot Engine Logo
+Copyright (c) 2017 Andrea Calabró
+
+This work is licensed under the Creative Commons Attribution 4.0 International
+license (CC BY 4.0 International): https://creativecommons.org/licenses/by/4.0/
\ No newline at end of file
diff --git a/evolve-die-repeat/assets/godot_engine_logo/logo_vertical_color_dark.png b/evolve-die-repeat/assets/godot_engine_logo/logo_vertical_color_dark.png
new file mode 100644
index 0000000..2c38732
Binary files /dev/null and b/evolve-die-repeat/assets/godot_engine_logo/logo_vertical_color_dark.png differ
diff --git a/evolve-die-repeat/assets/godot_engine_logo/logo_vertical_color_dark.png.import b/evolve-die-repeat/assets/godot_engine_logo/logo_vertical_color_dark.png.import
new file mode 100644
index 0000000..8af58aa
--- /dev/null
+++ b/evolve-die-repeat/assets/godot_engine_logo/logo_vertical_color_dark.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c3e3j1hcj8mnv"
+path="res://.godot/imported/logo_vertical_color_dark.png-056a6f9bbcf3b5b4e6d6f8f5f4aa19c6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/godot_engine_logo/logo_vertical_color_dark.png"
+dest_files=["res://.godot/imported/logo_vertical_color_dark.png-056a6f9bbcf3b5b4e6d6f8f5f4aa19c6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/assets/plugin_logo/LICENSE.txt b/evolve-die-repeat/assets/plugin_logo/LICENSE.txt
new file mode 100644
index 0000000..fef39a5
--- /dev/null
+++ b/evolve-die-repeat/assets/plugin_logo/LICENSE.txt
@@ -0,0 +1,5 @@
+Maaack's Game Template Logo
+Copyright (c) Marek Belski
+
+This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International
+license (CC BY-NC-ND 4.0 International): https://creativecommons.org/licenses/by-nc-nd/4.0
diff --git a/evolve-die-repeat/assets/plugin_logo/logo.png b/evolve-die-repeat/assets/plugin_logo/logo.png
new file mode 100644
index 0000000..ea70a32
Binary files /dev/null and b/evolve-die-repeat/assets/plugin_logo/logo.png differ
diff --git a/evolve-die-repeat/assets/plugin_logo/logo.png.import b/evolve-die-repeat/assets/plugin_logo/logo.png.import
new file mode 100644
index 0000000..3d23b67
--- /dev/null
+++ b/evolve-die-repeat/assets/plugin_logo/logo.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bcqn7vuiwedm2"
+path="res://.godot/imported/logo.png-31474a8ee58537d771c887e29197a8a3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/plugin_logo/logo.png"
+dest_files=["res://.godot/imported/logo.png-31474a8ee58537d771c887e29197a8a3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/evolve-die-repeat/default_bus_layout.tres b/evolve-die-repeat/default_bus_layout.tres
new file mode 100644
index 0000000..6300ac7
--- /dev/null
+++ b/evolve-die-repeat/default_bus_layout.tres
@@ -0,0 +1,15 @@
+[gd_resource type="AudioBusLayout" format=3 uid="uid://km3d1obvxfba"]
+
+[resource]
+bus/1/name = &"Music"
+bus/1/solo = false
+bus/1/mute = false
+bus/1/bypass_fx = false
+bus/1/volume_db = 0.0
+bus/1/send = &"Master"
+bus/2/name = &"SFX"
+bus/2/solo = false
+bus/2/mute = false
+bus/2/bypass_fx = false
+bus/2/volume_db = 0.0
+bus/2/send = &"Master"
diff --git a/evolve-die-repeat/override.cfg b/evolve-die-repeat/override.cfg
new file mode 100644
index 0000000..8ca7535
--- /dev/null
+++ b/evolve-die-repeat/override.cfg
@@ -0,0 +1,36 @@
+; Project settings override file.
+; Adds gamepad inputs to built-in actions.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+
+[input]
+
+ui_accept={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null)
+]
+}
+ui_cancel={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":6,"pressure":0.0,"pressed":true,"script":null)
+]
+}
+ui_page_up={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194323,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":9,"pressure":0.0,"pressed":true,"script":null)
+]
+}
+ui_page_down={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194324,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":10,"pressure":0.0,"pressed":true,"script":null)
+]
+}
diff --git a/evolve-die-repeat/project.godot b/evolve-die-repeat/project.godot
index 52116e7..3db6481 100644
--- a/evolve-die-repeat/project.godot
+++ b/evolve-die-repeat/project.godot
@@ -11,9 +11,36 @@ config_version=5
[application]
config/name="roguelike"
+run/main_scene="res://scenes/opening/opening.tscn"
config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg"
+[audio]
+
+buses/default_bus_layout="uid://km3d1obvxfba"
+
+[autoload]
+
+AppConfig="*uid://cjke6crjg14a0"
+SceneLoader="*uid://cbwmrnp0af35y"
+ProjectMusicController="*uid://r5t485lr3p7t"
+ProjectUISoundController="*uid://cc37235kj4384"
+
+[editor_plugins]
+
+enabled=PackedStringArray("res://addons/maaacks_game_template/plugin.cfg")
+
+[internationalization]
+
+locale/translations=PackedStringArray("res://addons/maaacks_game_template/base/translations/menus_translations.en.translation", "res://addons/maaacks_game_template/base/translations/menus_translations.fr.translation")
+
+[maaacks_game_template]
+
+disable_install_audio_busses=true
+disable_update_check=false
+disable_install_wizard=true
+copy_path="res://"
+
[physics]
3d/physics_engine="Jolt Physics"
diff --git a/evolve-die-repeat/resources/themes/expedition.tres b/evolve-die-repeat/resources/themes/expedition.tres
new file mode 100644
index 0000000..d547b3a
--- /dev/null
+++ b/evolve-die-repeat/resources/themes/expedition.tres
@@ -0,0 +1,98 @@
+[gd_resource type="Theme" format=3 uid="uid://cwn8yfatpkbyc"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g0sbc"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.823636, 0.744991, 0.659007, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.662913, 0.549096, 0.478248, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_krvwn"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.662913, 0.549096, 0.478248, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.466529, 0.360525, 0.333165, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gfyr3"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.466529, 0.360525, 0.333165, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.282353, 0.231067, 0.227161, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tytr8"]
+bg_color = Color(0.282353, 0.231067, 0.227161, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.133364, 0.133364, 0.133364, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wsakr"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.823636, 0.744991, 0.659007, 1)
+border_width_right = 2
+border_color = Color(0.662913, 0.549096, 0.478248, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1ngrn"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.662913, 0.549096, 0.478248, 1)
+border_width_right = 2
+border_color = Color(0.466529, 0.360525, 0.333165, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q0g5m"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.466529, 0.360525, 0.333165, 1)
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.282353, 0.231067, 0.227161, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+
+[resource]
+Button/styles/hover = SubResource("StyleBoxFlat_g0sbc")
+Button/styles/normal = SubResource("StyleBoxFlat_krvwn")
+Button/styles/pressed = SubResource("StyleBoxFlat_gfyr3")
+Panel/styles/panel = SubResource("StyleBoxFlat_tytr8")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_tytr8")
+TabContainer/styles/panel = SubResource("StyleBoxFlat_tytr8")
+TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_wsakr")
+TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_1ngrn")
+TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_q0g5m")
diff --git a/evolve-die-repeat/resources/themes/gravity.tres b/evolve-die-repeat/resources/themes/gravity.tres
new file mode 100644
index 0000000..8a54ba9
--- /dev/null
+++ b/evolve-die-repeat/resources/themes/gravity.tres
@@ -0,0 +1,116 @@
+[gd_resource type="Theme" format=3 uid="uid://d2aqcxb2jcjmv"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w50h3"]
+content_margin_left = 16.0
+content_margin_top = 4.0
+content_margin_right = 16.0
+content_margin_bottom = 4.0
+bg_color = Color(0.125911, 0.125911, 0.125911, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.95, 0.95, 0.95, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+corner_detail = 1
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6tkof"]
+content_margin_left = 16.0
+content_margin_top = 4.0
+content_margin_right = 16.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.95, 0.95, 0.95, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+corner_detail = 1
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ftays"]
+content_margin_left = 16.0
+content_margin_top = 4.0
+content_margin_right = 16.0
+content_margin_bottom = 4.0
+bg_color = Color(0.95, 0.95, 0.95, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.125911, 0.125911, 0.125911, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+corner_detail = 1
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sucf2"]
+bg_color = Color(0.0619267, 0.0619267, 0.0619266, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+corner_detail = 1
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u30hj"]
+content_margin_left = 8.0
+content_margin_right = 8.0
+bg_color = Color(0.125536, 0.125536, 0.125536, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.945281, 0.945281, 0.945281, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_detail = 1
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_h5y6c"]
+content_margin_left = 8.0
+content_margin_right = 8.0
+bg_color = Color(0.945281, 0.945281, 0.945281, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.0195315, 0.0195315, 0.0195315, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_detail = 1
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g0qsc"]
+content_margin_left = 8.0
+content_margin_right = 8.0
+bg_color = Color(0, 0, 0, 0.933333)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.945281, 0.945281, 0.945281, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_detail = 1
+
+[resource]
+Button/colors/font_color = Color(0.95, 0.95, 0.95, 1)
+Button/colors/font_focus_color = Color(0.95, 0.95, 0.95, 1)
+Button/colors/font_hover_color = Color(0.95, 0.95, 0.95, 1)
+Button/colors/font_pressed_color = Color(0.125911, 0.125911, 0.125911, 1)
+Button/styles/disabled = null
+Button/styles/focus = null
+Button/styles/hover = SubResource("StyleBoxFlat_w50h3")
+Button/styles/normal = SubResource("StyleBoxFlat_6tkof")
+Button/styles/pressed = SubResource("StyleBoxFlat_ftays")
+Panel/styles/panel = SubResource("StyleBoxFlat_sucf2")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_sucf2")
+TabContainer/colors/font_hovered_color = Color(0.945281, 0.945281, 0.945281, 1)
+TabContainer/colors/font_selected_color = Color(0.0195315, 0.0195315, 0.0195315, 1)
+TabContainer/colors/font_unselected_color = Color(0.945281, 0.945281, 0.945281, 1)
+TabContainer/styles/panel = SubResource("StyleBoxFlat_sucf2")
+TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_u30hj")
+TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_h5y6c")
+TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_g0qsc")
diff --git a/evolve-die-repeat/resources/themes/grow.tres b/evolve-die-repeat/resources/themes/grow.tres
new file mode 100644
index 0000000..d5123ac
--- /dev/null
+++ b/evolve-die-repeat/resources/themes/grow.tres
@@ -0,0 +1,98 @@
+[gd_resource type="Theme" format=3 uid="uid://chdvcaxwj0pmq"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_g0sbc"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.428961, 0.730226, 0.50528, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.298858, 0.546296, 0.363635, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_krvwn"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.298858, 0.546296, 0.363635, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.234842, 0.443383, 0.289887, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gfyr3"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.234842, 0.443383, 0.289887, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.234842, 0.443383, 0.289887, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tytr8"]
+bg_color = Color(0.191138, 0.370484, 0.238651, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.115392, 0.241196, 0.148848, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wsakr"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.428961, 0.730226, 0.50528, 1)
+border_width_right = 2
+border_color = Color(0.298858, 0.546296, 0.363635, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1ngrn"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.298858, 0.546296, 0.363635, 1)
+border_width_right = 2
+border_color = Color(0.234842, 0.443383, 0.289887, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q0g5m"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.234842, 0.443383, 0.289887, 1)
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.234842, 0.443383, 0.289887, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+
+[resource]
+Button/styles/hover = SubResource("StyleBoxFlat_g0sbc")
+Button/styles/normal = SubResource("StyleBoxFlat_krvwn")
+Button/styles/pressed = SubResource("StyleBoxFlat_gfyr3")
+Panel/styles/panel = SubResource("StyleBoxFlat_tytr8")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_tytr8")
+TabContainer/styles/panel = SubResource("StyleBoxFlat_tytr8")
+TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_wsakr")
+TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_1ngrn")
+TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_q0g5m")
diff --git a/evolve-die-repeat/resources/themes/lab.tres b/evolve-die-repeat/resources/themes/lab.tres
new file mode 100644
index 0000000..fb5f581
--- /dev/null
+++ b/evolve-die-repeat/resources/themes/lab.tres
@@ -0,0 +1,185 @@
+[gd_resource type="Theme" format=3 uid="uid://biak5xdp88vhg"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4b4gg"]
+content_margin_left = 12.0
+content_margin_right = 12.0
+content_margin_bottom = 14.0
+bg_color = Color(0.305882, 0.454902, 0.6, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 12
+border_color = Color(0.141176, 0.321569, 0.45098, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_l51yu"]
+content_margin_left = 12.0
+content_margin_right = 12.0
+content_margin_bottom = 14.0
+bg_color = Color(0.243137, 0.25098, 0.333333, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 12
+border_color = Color(0.219608, 0.176471, 0.207843, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e7ejt"]
+content_margin_left = 12.0
+content_margin_right = 12.0
+content_margin_bottom = 14.0
+bg_color = Color(0.121569, 0.176471, 0.211765, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 12
+border_color = Color(0.0784314, 0.121569, 0.145098, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ywrjj"]
+bg_color = Color(0.6, 0.6, 0.6, 0)
+border_width_left = 3
+border_width_top = 3
+border_width_bottom = 3
+corner_radius_top_left = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tyerd"]
+bg_color = Color(0.8, 0.8, 0.8, 1)
+border_width_left = 2
+border_width_top = 3
+border_width_bottom = 3
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_left = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ulcur"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.0784314, 0.121569, 0.145098, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.0431373, 0.0627451, 0.0862745, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_aliwb"]
+bg_color = Color(0.6, 0.6, 0.6, 0)
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tnl2j"]
+content_margin_bottom = 3.0
+bg_color = Color(0.8, 0.8, 0.8, 1)
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k5faf"]
+bg_color = Color(0.6, 0.6, 0.6, 0)
+border_width_left = 3
+border_width_top = 3
+border_width_right = 3
+border_width_bottom = 3
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5j0a2"]
+bg_color = Color(0.8, 0.8, 0.8, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_odiue"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.0784314, 0.121569, 0.145098, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.219608, 0.176471, 0.207843, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ghjya"]
+content_margin_left = 12.0
+content_margin_right = 12.0
+bg_color = Color(0.305882, 0.454902, 0.6, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_color = Color(0.141176, 0.321569, 0.45098, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m6y06"]
+content_margin_left = 12.0
+content_margin_right = 12.0
+bg_color = Color(0.243137, 0.25098, 0.333333, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_color = Color(0.219608, 0.176471, 0.207843, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5icga"]
+content_margin_left = 12.0
+content_margin_right = 12.0
+bg_color = Color(0.0784314, 0.121569, 0.145098, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_color = Color(0.121569, 0.176471, 0.211765, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+
+[sub_resource type="FontVariation" id="FontVariation_i860b"]
+spacing_top = 4
+spacing_bottom = 2
+
+[resource]
+default_font = SubResource("FontVariation_i860b")
+Button/styles/hover = SubResource("StyleBoxFlat_4b4gg")
+Button/styles/normal = SubResource("StyleBoxFlat_l51yu")
+Button/styles/pressed = SubResource("StyleBoxFlat_e7ejt")
+LeftStaminaBar/base_type = &"ProgressBar"
+LeftStaminaBar/styles/background = SubResource("StyleBoxFlat_ywrjj")
+LeftStaminaBar/styles/fill = SubResource("StyleBoxFlat_tyerd")
+Panel/styles/panel = SubResource("StyleBoxFlat_ulcur")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_ulcur")
+RightStaminaBar/base_type = &"ProgressBar"
+RightStaminaBar/styles/background = SubResource("StyleBoxFlat_aliwb")
+RightStaminaBar/styles/fill = SubResource("StyleBoxFlat_tnl2j")
+StaminaBar/base_type = &"ProgressBar"
+StaminaBar/styles/background = SubResource("StyleBoxFlat_k5faf")
+StaminaBar/styles/fill = SubResource("StyleBoxFlat_5j0a2")
+TabContainer/styles/panel = SubResource("StyleBoxFlat_odiue")
+TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_ghjya")
+TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_m6y06")
+TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_5icga")
diff --git a/evolve-die-repeat/resources/themes/lore.tres b/evolve-die-repeat/resources/themes/lore.tres
new file mode 100644
index 0000000..2cd1add
--- /dev/null
+++ b/evolve-die-repeat/resources/themes/lore.tres
@@ -0,0 +1,190 @@
+[gd_resource type="Theme" format=3 uid="uid://bv3cprtab3rmr"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8hxc2"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.631373, 0.52549, 0.619608, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.192157, 0.239216, 0.352941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_daw1f"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.839216, 0.933333, 1, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_r1yu6"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wnp2l"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0.192157, 0.239216, 0.352941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2ymfe"]
+bg_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pq3iw"]
+bg_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hvab5"]
+bg_color = Color(0.192157, 0.239216, 0.352941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_f05by"]
+bg_color = Color(0.192157, 0.239216, 0.352941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t1x62"]
+bg_color = Color(0.929412, 0.921569, 0.627451, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_w2bse"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.839216, 0.933333, 1, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7eahf"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.192157, 0.239216, 0.352941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5sk2t"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_00w47"]
+bg_color = Color(0.839216, 0.933333, 1, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gh53c"]
+bg_color = Color(0.631373, 0.52549, 0.619608, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.8, 0.8, 0.8, 0)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[resource]
+Button/colors/font_color = Color(0.839216, 0.933333, 1, 1)
+Button/colors/font_disabled_color = Color(0.192157, 0.239216, 0.352941, 1)
+Button/colors/font_focus_color = Color(0.839216, 0.933333, 1, 1)
+Button/colors/font_hover_color = Color(0.839216, 0.933333, 1, 1)
+Button/colors/font_hover_pressed_color = Color(0.839216, 0.933333, 1, 1)
+Button/colors/font_pressed_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+Button/styles/disabled = SubResource("StyleBoxFlat_8hxc2")
+Button/styles/hover = SubResource("StyleBoxFlat_daw1f")
+Button/styles/normal = SubResource("StyleBoxFlat_r1yu6")
+Button/styles/pressed = SubResource("StyleBoxFlat_wnp2l")
+DelayProgressBar/base_type = &"ProgressBar"
+DelayProgressBar/styles/fill = SubResource("StyleBoxFlat_2ymfe")
+Label/colors/font_color = Color(0.839216, 0.933333, 1, 1)
+Label/colors/font_outline_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+Label/constants/outline_size = 8
+LineEdit/colors/caret_color = Color(0.839216, 0.933333, 1, 1)
+LineEdit/colors/font_color = Color(0.929412, 0.921569, 0.627451, 1)
+LineEdit/colors/font_uneditable_color = Color(0.192157, 0.239216, 0.352941, 1)
+LineEdit/styles/normal = SubResource("StyleBoxFlat_pq3iw")
+LineEdit/styles/read_only = SubResource("StyleBoxFlat_pq3iw")
+Panel/styles/panel = SubResource("StyleBoxFlat_hvab5")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_hvab5")
+ProgressBar/styles/background = SubResource("StyleBoxFlat_f05by")
+ProgressBar/styles/fill = SubResource("StyleBoxFlat_t1x62")
+RichTextLabel/colors/default_color = Color(0.839216, 0.933333, 1, 1)
+RichTextLabel/colors/font_outline_color = Color(0.00392157, 0.0862745, 0.152941, 1)
+RichTextLabel/constants/outline_size = 8
+TabContainer/styles/panel = SubResource("StyleBoxFlat_hvab5")
+TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_w2bse")
+TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_7eahf")
+TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_5sk2t")
+TimerProgressBar/base_type = &"ProgressBar"
+TimerProgressBar/styles/fill = SubResource("StyleBoxFlat_00w47")
+WarningProgressBar/base_type = &"ProgressBar"
+WarningProgressBar/styles/fill = SubResource("StyleBoxFlat_gh53c")
diff --git a/evolve-die-repeat/resources/themes/steal_this_theme.tres b/evolve-die-repeat/resources/themes/steal_this_theme.tres
new file mode 100644
index 0000000..4b3399b
--- /dev/null
+++ b/evolve-die-repeat/resources/themes/steal_this_theme.tres
@@ -0,0 +1,560 @@
+[gd_resource type="Theme" format=3 uid="uid://wvt2cq02knm1"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7rtxy"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.698039, 0.133333, 0.203922, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hofdy"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 0)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.235294, 0.231373, 0.431373, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+expand_margin_left = 2.0
+expand_margin_top = 2.0
+expand_margin_right = 2.0
+expand_margin_bottom = 2.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0ahyh"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_83bj2"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.973535, 0.973535, 0.973535, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bjb6u"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.235294, 0.231373, 0.431373, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wv8md"]
+bg_color = Color(0.698039, 0.133333, 0.203922, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s1fdf"]
+bg_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tieq2"]
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rrxf3"]
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.235294, 0.231373, 0.431373, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2e0dr"]
+content_margin_top = 3.0
+content_margin_bottom = 3.0
+bg_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_fgisk"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fgisk"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5e2ta"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0383972, 0.0383972, 0.0383972, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7e08u"]
+bg_color = Color(0, 0, 0, 1)
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_whago"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.235294, 0.231373, 0.431373, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d8x3d"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.698039, 0.133333, 0.203922, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hofdy"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lmfyq"]
+bg_color = Color(0.698039, 0.133333, 0.203922, 1)
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 0)
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wv8md"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c4ulf"]
+bg_color = Color(0, 0, 0, 1)
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 0)
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8723n"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.235294, 0.231373, 0.431373, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_opsya"]
+bg_color = Color(0.235294, 0.231373, 0.431373, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8g14u"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0383972, 0.0383972, 0.0383972, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qnvbk"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_top = 2
+border_color = Color(0.698039, 0.133333, 0.203922, 1)
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_d8x3d"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4bfjk"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_top = 2
+border_color = Color(0.235294, 0.231373, 0.431373, 1)
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ojvr3"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+border_width_top = 2
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lop2v"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_top = 2
+border_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_0ahyh"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_83bj2"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rr4b1"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 0)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.235294, 0.231373, 0.431373, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+expand_margin_left = 2.0
+expand_margin_top = 2.0
+expand_margin_right = 2.0
+expand_margin_bottom = 2.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bcw1c"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0383972, 0.0383972, 0.0383972, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xi1kj"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rrcvo"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.235294, 0.231373, 0.431373, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wvge0"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.235294, 0.231373, 0.431373, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v0ggg"]
+bg_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kvvmu"]
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.973535, 0.973535, 0.973535, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rum38"]
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.235294, 0.231373, 0.431373, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5wbgk"]
+content_margin_left = 3.0
+content_margin_right = 3.0
+bg_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_s1fdf"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x127s"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0.698039, 0.133333, 0.203922, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qjhx0"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.698039, 0.133333, 0.203922, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_u427p"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.698039, 0.133333, 0.203922, 1)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[resource]
+BoldLabel/base_type = &"Label"
+BoldLabel/fonts/font = null
+Button/colors/font_color = Color(0, 0, 0, 1)
+Button/colors/font_disabled_color = Color(0.698039, 0.133333, 0.203922, 1)
+Button/colors/font_focus_color = Color(0, 0, 0, 1)
+Button/colors/font_hover_color = Color(0.973535, 0.973535, 0.973535, 1)
+Button/colors/font_hover_pressed_color = Color(0.973535, 0.973535, 0.973535, 1)
+Button/colors/font_pressed_color = Color(0.235294, 0.231373, 0.431373, 1)
+Button/styles/disabled = SubResource("StyleBoxFlat_7rtxy")
+Button/styles/focus = SubResource("StyleBoxFlat_hofdy")
+Button/styles/hover = SubResource("StyleBoxFlat_0ahyh")
+Button/styles/normal = SubResource("StyleBoxFlat_83bj2")
+Button/styles/pressed = SubResource("StyleBoxFlat_bjb6u")
+DisabledLabel/base_type = &"Label"
+DisabledLabel/colors/font_color = Color(0, 0, 0, 1)
+DisabledLabel/font_sizes/font_size = 24
+DisabledLabel/fonts/font = null
+DisabledLabel/styles/normal = SubResource("StyleBoxFlat_wv8md")
+HScrollBar/styles/grabber = SubResource("StyleBoxFlat_s1fdf")
+HScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_tieq2")
+HScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_rrxf3")
+HScrollBar/styles/scroll = SubResource("StyleBoxFlat_2e0dr")
+HScrollBar/styles/scroll_focus = SubResource("StyleBoxEmpty_fgisk")
+LocationName/base_type = &"Label"
+LocationName/colors/font_color = Color(0.973535, 0.973535, 0.973535, 1)
+LocationName/font_sizes/font_size = 22
+LocationName/fonts/font = null
+LocationType/base_type = &"Label"
+LocationType/colors/font_color = Color(0.235294, 0.231373, 0.431373, 1)
+LocationType/fonts/font = null
+Panel/styles/panel = SubResource("StyleBoxFlat_fgisk")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_5e2ta")
+ProgressBar/styles/background = SubResource("StyleBoxFlat_7e08u")
+ProgressBar/styles/fill = SubResource("StyleBoxFlat_whago")
+ProgressBarBad/base_type = &"ProgressBar"
+ProgressBarBad/styles/fill = SubResource("StyleBoxFlat_d8x3d")
+ProgressBarDelay/base_type = &"ProgressBar"
+ProgressBarDelay/styles/background = SubResource("StyleBoxEmpty_hofdy")
+ProgressBarDelay/styles/fill = SubResource("StyleBoxFlat_lmfyq")
+ProgressBarDelayHovered/base_type = &"ProgressBar"
+ProgressBarDelayHovered/styles/background = SubResource("StyleBoxEmpty_wv8md")
+ProgressBarDelayHovered/styles/fill = SubResource("StyleBoxFlat_c4ulf")
+ProgressBarGood/base_type = &"ProgressBar"
+ProgressBarGood/styles/fill = SubResource("StyleBoxFlat_8723n")
+RichTextLabel/fonts/bold_font = null
+RichTextLabel/fonts/bold_italics_font = null
+RichTextLabel/fonts/italics_font = null
+SelectedLabel/base_type = &"Label"
+SelectedLabel/colors/font_color = Color(0.973535, 0.973535, 0.973535, 1)
+SelectedLabel/font_sizes/font_size = 24
+SelectedLabel/fonts/font = null
+SelectedLabel/styles/normal = SubResource("StyleBoxFlat_opsya")
+TabContainer/colors/font_disabled_color = Color(0.698039, 0.133333, 0.203922, 1)
+TabContainer/colors/font_hovered_color = Color(0.235294, 0.231373, 0.431373, 1)
+TabContainer/colors/font_selected_color = Color(0.973535, 0.973535, 0.973535, 1)
+TabContainer/colors/font_unselected_color = Color(0.973535, 0.973535, 0.973535, 1)
+TabContainer/styles/panel = SubResource("StyleBoxFlat_8g14u")
+TabContainer/styles/tab_disabled = SubResource("StyleBoxFlat_qnvbk")
+TabContainer/styles/tab_focus = SubResource("StyleBoxEmpty_d8x3d")
+TabContainer/styles/tab_hovered = SubResource("StyleBoxFlat_4bfjk")
+TabContainer/styles/tab_selected = SubResource("StyleBoxFlat_ojvr3")
+TabContainer/styles/tab_unselected = SubResource("StyleBoxFlat_lop2v")
+Tree/colors/children_hl_line_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+Tree/colors/font_color = Color(0.973535, 0.973535, 0.973535, 1)
+Tree/colors/font_disabled_color = Color(0.698039, 0.133333, 0.203922, 1)
+Tree/colors/font_hovered_color = Color(0.235294, 0.231373, 0.431373, 1)
+Tree/colors/font_selected_color = Color(0.973535, 0.973535, 0.973535, 1)
+Tree/colors/guide_color = Color(0, 0, 0, 0)
+Tree/colors/parent_hl_line_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+Tree/colors/relationship_line_color = Color(0.0392157, 0.0392157, 0.0392157, 1)
+Tree/constants/inner_item_margin_left = 4
+Tree/constants/inner_item_margin_right = 4
+Tree/constants/item_margin = 0
+Tree/styles/button_hover = SubResource("StyleBoxEmpty_0ahyh")
+Tree/styles/button_pressed = SubResource("StyleBoxEmpty_83bj2")
+Tree/styles/focus = SubResource("StyleBoxFlat_rr4b1")
+Tree/styles/hovered = SubResource("StyleBoxFlat_bcw1c")
+Tree/styles/panel = SubResource("StyleBoxFlat_xi1kj")
+Tree/styles/selected = SubResource("StyleBoxFlat_rrcvo")
+Tree/styles/selected_focus = SubResource("StyleBoxFlat_wvge0")
+VScrollBar/styles/grabber = SubResource("StyleBoxFlat_v0ggg")
+VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_kvvmu")
+VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_rum38")
+VScrollBar/styles/scroll = SubResource("StyleBoxFlat_5wbgk")
+VScrollBar/styles/scroll_focus = SubResource("StyleBoxEmpty_s1fdf")
+WaitingButton/base_type = &"Button"
+WaitingButton/colors/font_color = Color(0.698039, 0.133333, 0.203922, 1)
+WaitingButton/colors/font_focus_color = Color(0.698039, 0.133333, 0.203922, 1)
+WaitingButton/colors/font_hover_color = Color(0, 0, 0, 1)
+WaitingButton/colors/font_hover_pressed_color = Color(0.698039, 0.133333, 0.203922, 1)
+WaitingButton/colors/font_pressed_color = Color(0.698039, 0.133333, 0.203922, 1)
+WaitingButton/styles/hover = SubResource("StyleBoxFlat_x127s")
+WaitingButton/styles/normal = SubResource("StyleBoxFlat_qjhx0")
+WaitingButton/styles/pressed = SubResource("StyleBoxFlat_u427p")
diff --git a/evolve-die-repeat/scenes/credits/credits_label.tscn b/evolve-die-repeat/scenes/credits/credits_label.tscn
new file mode 100644
index 0000000..7cf9424
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/credits_label.tscn
@@ -0,0 +1,63 @@
+[gd_scene format=3 uid="uid://cs7gpqjmiqrk0"]
+
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/labels/credits_label.gd" id="1_yhcaj"]
+
+[node name="CreditsLabel" type="RichTextLabel" unique_id=525943618]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 5
+bbcode_enabled = true
+text = "[font_size=48]Collaborators[/font_size]
+
+[font_size=32]Role[/font_size]
+Person 1
+Person 2
+[url=]Person w/ Link[/url]
+
+[font_size=48]Sourced[/font_size]
+[font_size=32]Asset Type[/font_size]
+[font_size=24]Use Case[/font_size]
+Author: [url=]Name[/url]
+Source: [url=]Domain : webpage.html[/url]
+License: [url=]License[/url]
+
+[font_size=24]Godot Engine Logo[/font_size]
+Author: Andrea Calabró
+Source: [url=https://godotengine.org/press/]godotengine.org : press[/url]
+License: [url=https://github.com/godotengine/godot/blob/master/LOGO_LICENSE.txt]CC BY 4.0 International[/url]
+
+[font_size=48]Tools[/font_size]
+[font_size=24]Godot[/font_size]
+[img=80]res:///assets/godot_engine_logo/logo_vertical_color_dark.png[/img]
+Author: [url=https://godotengine.org/contact]Juan Linietsky, Ariel Manzur, and contributors[/url]
+Source: [url=https://godotengine.org/]godotengine.org[/url]
+License: [url=https://github.com/godotengine/godot/blob/master/LICENSE.txt]MIT License[/url]
+
+[font_size=24]Godot Game Template[/font_size]
+[img=80]res:///assets/plugin_logo/logo.png[/img]
+Author: [url=https://github.com/Maaack/Godot-Game-Template/graphs/contributors]Marek Belski and contributors[/url]
+Source: [url=https://github.com/Maaack/Godot-Game-Template]github: Godot-Game-Template[/url]
+License: [url=LICENSE.txt]MIT License[/url]
+
+[font_size=24]Git[/font_size]
+[img=80]res:///assets/git_logo/Git-Logo-2Color.png[/img]
+Author: [url=https://github.com/torvalds]Linus Torvalds[/url]
+Source: [url=https://git-scm.com/downloads]git-scm.com[/url]
+License: [url=https://opensource.org/licenses/GPL-2.0]GNU General Public License version 2[/url]
+"
+fit_content = true
+scroll_active = false
+horizontal_alignment = 1
+script = ExtResource("1_yhcaj")
+attribution_file_path = "res://ATTRIBUTION.md"
+h1_font_size = 64
+h2_font_size = 48
+h3_font_size = 32
+h4_font_size = 24
+h5_font_size = 20
+h6_font_size = 16
+max_image_width = 80
diff --git a/evolve-die-repeat/scenes/credits/scrollable_credits.gd b/evolve-die-repeat/scenes/credits/scrollable_credits.gd
new file mode 100644
index 0000000..6de019f
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/scrollable_credits.gd
@@ -0,0 +1,29 @@
+@tool
+extends Control
+
+@onready var credits_label : RichTextLabel = %CreditsLabel
+
+@export var input_scroll_speed : float = 10.0
+
+var _line_number : float = 0
+
+func _on_visibility_changed() -> void:
+ if visible:
+ credits_label.scroll_to_line(0)
+ credits_label.grab_focus()
+
+func _ready() -> void:
+ visibility_changed.connect(_on_visibility_changed)
+
+func _process(delta : float) -> void:
+ if Engine.is_editor_hint() or not visible:
+ return
+ var input_axis = Input.get_axis("ui_up", "ui_down")
+ if abs(input_axis) > 0.5:
+ _line_number += input_axis * delta * input_scroll_speed
+ var max_lines = credits_label.get_line_count() - credits_label.get_visible_line_count()
+ if _line_number < 0:
+ _line_number = 0
+ if _line_number > max_lines:
+ _line_number = max_lines
+ credits_label.scroll_to_line(round(_line_number))
diff --git a/evolve-die-repeat/scenes/credits/scrollable_credits.gd.uid b/evolve-die-repeat/scenes/credits/scrollable_credits.gd.uid
new file mode 100644
index 0000000..6f6cf6a
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/scrollable_credits.gd.uid
@@ -0,0 +1 @@
+uid://bw18ru8waloim
diff --git a/evolve-die-repeat/scenes/credits/scrollable_credits.tscn b/evolve-die-repeat/scenes/credits/scrollable_credits.tscn
new file mode 100644
index 0000000..269326d
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/scrollable_credits.tscn
@@ -0,0 +1,19 @@
+[gd_scene format=3 uid="uid://bw6rkyxtffvmp"]
+
+[ext_resource type="Script" path="res://scenes/credits/scrollable_credits.gd" id="1_jnbjt"]
+[ext_resource type="PackedScene" path="res://scenes/credits/credits_label.tscn" id="2_35kdw"]
+
+[node name="ScrollableCredits" type="Control" unique_id=1649718614]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_jnbjt")
+
+[node name="CreditsLabel" parent="." unique_id=967293869 instance=ExtResource("2_35kdw")]
+unique_name_in_owner = true
+layout_mode = 1
+fit_content = false
+scroll_active = true
diff --git a/evolve-die-repeat/scenes/credits/scrolling_credits.gd b/evolve-die-repeat/scenes/credits/scrolling_credits.gd
new file mode 100644
index 0000000..78ead19
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/scrolling_credits.gd
@@ -0,0 +1,92 @@
+@tool
+extends Control
+
+signal end_reached
+
+@export var auto_scroll_speed: float = 60.0
+@export var input_scroll_speed : float = 400.0
+@export var scroll_restart_delay : float = 1.5
+@export var scroll_paused : bool = false
+
+var timer : Timer = Timer.new()
+var _current_scroll_position : float = 0.0
+
+@onready var header_space : Control = %HeaderSpace
+@onready var footer_space : Control = %FooterSpace
+@onready var credits_label : Control = %CreditsLabel
+@onready var scroll_container : ScrollContainer = %ScrollContainer
+
+func set_header_and_footer() -> void:
+ header_space.custom_minimum_size.y = size.y
+ footer_space.custom_minimum_size.y = size.y
+ credits_label.custom_minimum_size.x = size.x
+
+func _on_resized() -> void:
+ set_header_and_footer()
+ _current_scroll_position = scroll_container.scroll_vertical
+
+func _end_reached() -> void:
+ scroll_paused = true
+ end_reached.emit()
+
+func is_end_reached() -> bool:
+ var _end_of_credits_vertical = credits_label.size.y + header_space.size.y
+ return scroll_container.scroll_vertical > _end_of_credits_vertical
+
+func _check_end_reached() -> void:
+ if not is_end_reached():
+ return
+ _end_reached()
+
+func _scroll_container(amount : float) -> void:
+ if not visible or scroll_paused:
+ return
+ _current_scroll_position += amount
+ scroll_container.scroll_vertical = round(_current_scroll_position)
+ _check_end_reached()
+
+func _on_gui_input(event : InputEvent) -> void:
+ # Captures the mouse scroll wheel input event
+ if event is InputEventMouseButton:
+ scroll_paused = true
+ _start_scroll_restart_timer()
+ _check_end_reached()
+
+func _on_scroll_started() -> void:
+ # Captures the touch input event
+ scroll_paused = true
+ _start_scroll_restart_timer()
+
+func _start_scroll_restart_timer() -> void:
+ timer.start(scroll_restart_delay)
+
+func _on_scroll_restart_timer_timeout() -> void:
+ _current_scroll_position = scroll_container.scroll_vertical
+ scroll_paused = false
+
+func _on_visibility_changed() -> void:
+ if visible:
+ scroll_container.scroll_vertical = 0
+ _current_scroll_position = scroll_container.scroll_vertical
+ scroll_paused = false
+
+func _ready() -> void:
+ scroll_container.scroll_started.connect(_on_scroll_started)
+ gui_input.connect(_on_gui_input)
+ resized.connect(_on_resized)
+ visibility_changed.connect(_on_visibility_changed)
+ timer.timeout.connect(_on_scroll_restart_timer_timeout)
+ set_header_and_footer()
+ add_child(timer)
+ scroll_paused = false
+
+
+func _process(delta : float) -> void:
+ var input_axis = Input.get_axis("ui_up", "ui_down")
+ if input_axis != 0:
+ _scroll_container(input_axis * input_scroll_speed * delta)
+ else:
+ _scroll_container(auto_scroll_speed * delta)
+
+func _exit_tree() -> void:
+ _current_scroll_position = scroll_container.scroll_vertical
diff --git a/evolve-die-repeat/scenes/credits/scrolling_credits.gd.uid b/evolve-die-repeat/scenes/credits/scrolling_credits.gd.uid
new file mode 100644
index 0000000..96703a5
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/scrolling_credits.gd.uid
@@ -0,0 +1 @@
+uid://cfy35bmhpaiq6
diff --git a/evolve-die-repeat/scenes/credits/scrolling_credits.tscn b/evolve-die-repeat/scenes/credits/scrolling_credits.tscn
new file mode 100644
index 0000000..2389e67
--- /dev/null
+++ b/evolve-die-repeat/scenes/credits/scrolling_credits.tscn
@@ -0,0 +1,39 @@
+[gd_scene format=3 uid="uid://b0oyosa2d7fav"]
+
+[ext_resource type="Script" path="res://scenes/credits/scrolling_credits.gd" id="1_hgjqm"]
+[ext_resource type="PackedScene" path="res://scenes/credits/credits_label.tscn" id="2_cxj1u"]
+
+[node name="ScrollingCredits" type="Control" unique_id=1162279489]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_hgjqm")
+
+[node name="ScrollContainer" type="ScrollContainer" parent="." unique_id=1039720129]
+unique_name_in_owner = true
+layout_mode = 0
+anchor_right = 1.0
+anchor_bottom = 1.0
+horizontal_scroll_mode = 0
+vertical_scroll_mode = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer" unique_id=2067921385]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="HeaderSpace" type="Control" parent="ScrollContainer/VBoxContainer" unique_id=1552560353]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 720)
+layout_mode = 2
+
+[node name="CreditsLabel" parent="ScrollContainer/VBoxContainer" unique_id=1486745388 instance=ExtResource("2_cxj1u")]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="FooterSpace" type="Control" parent="ScrollContainer/VBoxContainer" unique_id=1703782196]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 720)
+layout_mode = 2
diff --git a/evolve-die-repeat/scenes/end_credits/end_credits.gd b/evolve-die-repeat/scenes/end_credits/end_credits.gd
new file mode 100644
index 0000000..c7630ed
--- /dev/null
+++ b/evolve-die-repeat/scenes/end_credits/end_credits.gd
@@ -0,0 +1,61 @@
+@tool
+extends "res://scenes/credits/scrolling_credits.gd"
+
+## Defines the path to the main menu. Hides the Main Menu button if not set.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var main_menu_scene_path : String
+## This option forces the mouse to be visible when the menu shows up.
+## Useful for games that capture the mouse, and don't automatically return it.
+@export var force_mouse_mode_visible : bool = false
+
+@onready var end_message_panel = %EndMessagePanel
+@onready var exit_button = %ExitButton
+@onready var menu_button = %MenuButton
+@onready var init_mouse_filter : MouseFilter = mouse_filter
+
+func get_main_menu_scene_path() -> String:
+ if main_menu_scene_path.is_empty():
+ return AppConfig.main_menu_scene_path
+ return main_menu_scene_path
+
+func _end_reached() -> void:
+ end_message_panel.show()
+ mouse_filter = Control.MOUSE_FILTER_STOP
+ if force_mouse_mode_visible:
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ super._end_reached()
+
+func load_main_menu() -> void:
+ SceneLoader.load_scene(get_main_menu_scene_path())
+
+func exit_game() -> void:
+ if OS.has_feature("web"):
+ load_main_menu()
+ get_tree().quit()
+
+func _on_visibility_changed() -> void:
+ if visible:
+ end_message_panel.hide()
+ mouse_filter = init_mouse_filter
+ super._on_visibility_changed()
+
+func _ready() -> void:
+ if get_main_menu_scene_path().is_empty():
+ menu_button.hide()
+ if OS.has_feature("web"):
+ exit_button.hide()
+ end_message_panel.hide()
+ super._ready()
+
+func _unhandled_input(event : InputEvent) -> void:
+ if event.is_action_released("ui_cancel"):
+ if not end_message_panel.visible:
+ _end_reached()
+ else:
+ exit_game()
+
+func _on_exit_button_pressed():
+ exit_game()
+
+func _on_menu_button_pressed():
+ load_main_menu()
diff --git a/evolve-die-repeat/scenes/end_credits/end_credits.gd.uid b/evolve-die-repeat/scenes/end_credits/end_credits.gd.uid
new file mode 100644
index 0000000..943eabd
--- /dev/null
+++ b/evolve-die-repeat/scenes/end_credits/end_credits.gd.uid
@@ -0,0 +1 @@
+uid://qb0nxsu5pjsm
diff --git a/evolve-die-repeat/scenes/end_credits/end_credits.tscn b/evolve-die-repeat/scenes/end_credits/end_credits.tscn
new file mode 100644
index 0000000..8270c63
--- /dev/null
+++ b/evolve-die-repeat/scenes/end_credits/end_credits.tscn
@@ -0,0 +1,88 @@
+[gd_scene format=3 uid="uid://chy2p20a0je14"]
+
+[ext_resource type="PackedScene" path="res://scenes/credits/scrolling_credits.tscn" id="1_0sycx"]
+[ext_resource type="Script" path="res://scenes/end_credits/end_credits.gd" id="2_2v40y"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/music_players/background_music_player.tscn" id="3_bfvdl"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="4_1kqi1"]
+
+[node name="EndCredits" unique_id=613298766 instance=ExtResource("1_0sycx")]
+script = ExtResource("2_2v40y")
+main_menu_scene_path = ""
+force_mouse_mode_visible = false
+
+[node name="BackgroundMusicPlayer" parent="." index="0" unique_id=1411252862 instance=ExtResource("3_bfvdl")]
+
+[node name="BackgroundColor" type="ColorRect" parent="." index="1" unique_id=813188635]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 1)
+
+[node name="BackgroundTextureRect" type="TextureRect" parent="." index="2" unique_id=1166831285]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
+stretch_mode = 5
+
+[node name="CenterContainer" type="CenterContainer" parent="." index="4" unique_id=535959837]
+layout_mode = 0
+anchor_right = 1.0
+anchor_bottom = 1.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+mouse_filter = 2
+
+[node name="EndMessagePanel" type="Panel" parent="CenterContainer" index="0" unique_id=680051131]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(360, 120)
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/EndMessagePanel" index="0" unique_id=899651798]
+layout_mode = 0
+anchor_right = 1.0
+anchor_bottom = 1.0
+
+[node name="ThankPlayer" type="Label" parent="CenterContainer/EndMessagePanel/VBoxContainer" index="0" unique_id=1020885934]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+text = "Thanks for playing!"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="CenterContainer" type="CenterContainer" parent="CenterContainer/EndMessagePanel/VBoxContainer" index="1" unique_id=1208569595]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer" index="0" unique_id=1415249609]
+custom_minimum_size = Vector2(256, 0)
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 16
+script = ExtResource("4_1kqi1")
+
+[node name="ExitButton" type="Button" parent="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer" index="0" unique_id=1382070624]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+text = "Exit"
+
+[node name="MenuButton" type="Button" parent="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer" index="1" unique_id=1504498101]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+text = "Menu"
+
+[connection signal="pressed" from="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"]
+[connection signal="pressed" from="CenterContainer/EndMessagePanel/VBoxContainer/CenterContainer/HBoxContainer/MenuButton" to="." method="_on_menu_button_pressed"]
diff --git a/evolve-die-repeat/scenes/game_scene/configurable_sub_viewport.gd b/evolve-die-repeat/scenes/game_scene/configurable_sub_viewport.gd
new file mode 100644
index 0000000..105e9f6
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/configurable_sub_viewport.gd
@@ -0,0 +1,12 @@
+extends SubViewport
+## Script to apply the anti-aliasing setting from [PlayerConfig] to a [SubViewport].
+
+## The name of the anti-aliasing variable in the [ConfigFile].
+@export var anti_aliasing_key : StringName = "Anti-aliasing"
+## The name of the section of the anti-aliasing variable in the [ConfigFile].
+@export var video_section : StringName = AppSettings.VIDEO_SECTION
+
+func _ready() -> void:
+ var anti_aliasing : int = PlayerConfig.get_config(video_section, anti_aliasing_key, Viewport.MSAA_DISABLED)
+ msaa_2d = anti_aliasing as MSAA
+ msaa_3d = anti_aliasing as MSAA
diff --git a/evolve-die-repeat/scenes/game_scene/configurable_sub_viewport.gd.uid b/evolve-die-repeat/scenes/game_scene/configurable_sub_viewport.gd.uid
new file mode 100644
index 0000000..ab3db5f
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/configurable_sub_viewport.gd.uid
@@ -0,0 +1 @@
+uid://cxalcykynhnqa
diff --git a/evolve-die-repeat/scenes/game_scene/game_timer.gd b/evolve-die-repeat/scenes/game_scene/game_timer.gd
new file mode 100644
index 0000000..6641bfe
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/game_timer.gd
@@ -0,0 +1,27 @@
+extends Node
+
+var play_time : int
+var total_time : int
+
+func _add_timers() -> void:
+ var play_timer := Timer.new()
+ play_timer.one_shot = false
+ play_timer.process_mode = Node.PROCESS_MODE_PAUSABLE
+ play_timer.timeout.connect(func() : play_time += 1)
+ add_child(play_timer)
+ play_timer.start(1)
+ var total_timer := Timer.new()
+ total_timer.one_shot = false
+ total_timer.process_mode = Node.PROCESS_MODE_ALWAYS
+ total_timer.timeout.connect(func() : total_time += 1)
+ add_child(total_timer)
+ total_timer.start(1)
+
+func _enter_tree() -> void:
+ _add_timers()
+
+func _exit_tree() -> void:
+ var game_state := GameState.get_or_create_state()
+ game_state.play_time += play_time
+ game_state.total_time += total_time
+ GlobalState.save()
diff --git a/evolve-die-repeat/scenes/game_scene/game_timer.gd.uid b/evolve-die-repeat/scenes/game_scene/game_timer.gd.uid
new file mode 100644
index 0000000..a5aab1c
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/game_timer.gd.uid
@@ -0,0 +1 @@
+uid://4mvjvxsdahll
diff --git a/evolve-die-repeat/scenes/game_scene/game_ui.tscn b/evolve-die-repeat/scenes/game_scene/game_ui.tscn
new file mode 100644
index 0000000..3b934d3
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/game_ui.tscn
@@ -0,0 +1,71 @@
+[gd_scene format=3 uid="uid://bumbxg3bfrwtd"]
+
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/pause_menu_controller.gd" id="1_bmy5n"]
+[ext_resource type="PackedScene" path="res://scenes/windows/pause_menu_layer.tscn" id="2_7aihr"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/music_players/background_music_player.tscn" id="3_3re04"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/extras/scripts/level_loader.gd" id="4_ascoc"]
+[ext_resource type="Script" path="res://scripts/level_and_state_manager.gd" id="5_wr4n6"]
+[ext_resource type="PackedScene" path="res://scenes/windows/game_won_window.tscn" id="6_ukb12"]
+[ext_resource type="PackedScene" path="res://scenes/windows/level_lost_window.tscn" id="7_i7m4m"]
+[ext_resource type="PackedScene" path="res://scenes/windows/level_won_window.tscn" id="8_rkfhe"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/extras/scripts/scene_lister.gd" id="9_g2ndc"]
+[ext_resource type="PackedScene" path="res://scenes/loading_screen/level_loading_screen.tscn" id="10_0v86b"]
+[ext_resource type="Script" path="res://scenes/game_scene/configurable_sub_viewport.gd" id="11_ulq3g"]
+[ext_resource type="Script" path="res://scenes/game_scene/game_timer.gd" id="12_gpor5"]
+
+[node name="GameUI" type="Control" unique_id=1609552811]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="PauseMenuController" type="Node" parent="." unique_id=1685173287 node_paths=PackedStringArray("focused_viewport")]
+script = ExtResource("1_bmy5n")
+pause_menu_packed = ExtResource("2_7aihr")
+focused_viewport = NodePath("../ViewportContainer/ConfigurableSubViewport")
+
+[node name="BackgroundMusicPlayer" parent="." unique_id=986713811 instance=ExtResource("3_3re04")]
+
+[node name="LevelLoader" type="Node" parent="." unique_id=1047451806 node_paths=PackedStringArray("level_container", "level_loading_screen")]
+script = ExtResource("4_ascoc")
+level_container = NodePath("../ViewportContainer/ConfigurableSubViewport")
+level_loading_screen = NodePath("../LevelLoadingScreen")
+
+[node name="LevelManager" type="Node" parent="." unique_id=1350972375 node_paths=PackedStringArray("level_loader", "scene_lister")]
+script = ExtResource("5_wr4n6")
+level_loader = NodePath("../LevelLoader")
+starting_level_path = "res://scenes/game_scene/levels/level_1.tscn"
+scene_lister = NodePath("SceneLister")
+game_won_scene = ExtResource("6_ukb12")
+level_lost_scene = ExtResource("7_i7m4m")
+level_won_scene = ExtResource("8_rkfhe")
+
+[node name="SceneLister" type="Node" parent="LevelManager" unique_id=673614733]
+script = ExtResource("9_g2ndc")
+files = Array[String](["res://scenes/game_scene/levels/level_1.tscn", "res://scenes/game_scene/levels/level_2.tscn", "res://scenes/game_scene/levels/level_3.tscn"])
+directory = "res://scenes/game_scene/levels"
+
+[node name="LevelLoadingScreen" parent="." unique_id=816430938 instance=ExtResource("10_0v86b")]
+visible = false
+
+[node name="ViewportContainer" type="SubViewportContainer" parent="." unique_id=1831513653]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+stretch = true
+
+[node name="ConfigurableSubViewport" type="SubViewport" parent="ViewportContainer" unique_id=204786699]
+handle_input_locally = false
+audio_listener_enable_2d = true
+audio_listener_enable_3d = true
+size = Vector2i(1280, 720)
+render_target_update_mode = 4
+script = ExtResource("11_ulq3g")
+
+[node name="GameTimer" type="Node" parent="." unique_id=116840261]
+script = ExtResource("12_gpor5")
diff --git a/evolve-die-repeat/scenes/game_scene/input_display_label.gd b/evolve-die-repeat/scenes/game_scene/input_display_label.gd
new file mode 100644
index 0000000..a57c4d2
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/input_display_label.gd
@@ -0,0 +1,21 @@
+extends Label
+
+@onready var action_names := AppSettings.get_action_names()
+
+func _get_inputs_as_string() -> String:
+ var all_inputs : String = ""
+ var is_first : bool = true
+ for action_name in action_names:
+ if Input.is_action_pressed(action_name):
+ if is_first:
+ is_first = false
+ all_inputs += action_name
+ else:
+ all_inputs += " + " + action_name
+ return all_inputs
+
+func _process(_delta : float) -> void:
+ if Input.is_anything_pressed():
+ text = _get_inputs_as_string()
+ else:
+ text = ""
diff --git a/evolve-die-repeat/scenes/game_scene/input_display_label.gd.uid b/evolve-die-repeat/scenes/game_scene/input_display_label.gd.uid
new file mode 100644
index 0000000..85b7474
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/input_display_label.gd.uid
@@ -0,0 +1 @@
+uid://jiyup3v57kj0
diff --git a/evolve-die-repeat/scenes/game_scene/levels/level.gd b/evolve-die-repeat/scenes/game_scene/levels/level.gd
new file mode 100644
index 0000000..d975690
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/levels/level.gd
@@ -0,0 +1,37 @@
+extends Node
+
+signal level_lost
+signal level_won(level_path : String)
+@warning_ignore("unused_signal")
+signal level_changed(level_path : String)
+
+## Optional path to the next level if using an open world level system.
+@export_file("*.tscn") var next_level_path : String
+
+var level_state : LevelState
+
+func _on_lose_button_pressed() -> void:
+ level_lost.emit()
+
+func _on_win_button_pressed() -> void:
+ level_won.emit(next_level_path)
+
+func open_tutorials() -> void:
+ %TutorialManager.open_tutorials()
+ level_state.tutorial_read = true
+ GlobalState.save()
+
+func _ready() -> void:
+ level_state = GameState.get_level_state(scene_file_path)
+ %ColorPickerButton.color = level_state.color
+ %BackgroundColor.color = level_state.color
+ if not level_state.tutorial_read:
+ open_tutorials()
+
+func _on_color_picker_button_color_changed(color : Color) -> void:
+ %BackgroundColor.color = color
+ level_state.color = color
+ GlobalState.save()
+
+func _on_tutorial_button_pressed() -> void:
+ open_tutorials()
diff --git a/evolve-die-repeat/scenes/game_scene/levels/level.gd.uid b/evolve-die-repeat/scenes/game_scene/levels/level.gd.uid
new file mode 100644
index 0000000..159cfa4
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/levels/level.gd.uid
@@ -0,0 +1 @@
+uid://efly2n6s1ers
diff --git a/evolve-die-repeat/scenes/game_scene/levels/level_1.tscn b/evolve-die-repeat/scenes/game_scene/levels/level_1.tscn
new file mode 100644
index 0000000..4b874a4
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/levels/level_1.tscn
@@ -0,0 +1,101 @@
+[gd_scene format=3 uid="uid://rkbotocsegqk"]
+
+[ext_resource type="Script" path="res://scenes/game_scene/levels/level.gd" id="1_623lg"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_sycfm"]
+[ext_resource type="Script" path="res://scenes/game_scene/input_display_label.gd" id="3_pmyid"]
+[ext_resource type="Script" path="res://scenes/game_scene/tutorial_manager.gd" id="4_bsimd"]
+[ext_resource type="PackedScene" path="res://scenes/game_scene/tutorials/tutorial_1.tscn" id="5_fmabn"]
+
+[node name="Level1" type="Control" unique_id=235822082]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_623lg")
+next_level_path = "res://scenes/game_scene/levels/level_2.tscn"
+
+[node name="BackgroundColor" type="ColorRect" parent="." unique_id=1389548597]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 1)
+
+[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1051608179]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 32
+theme_override_constants/margin_top = 32
+theme_override_constants/margin_right = 32
+theme_override_constants/margin_bottom = 32
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=1696412727]
+layout_mode = 2
+theme_override_constants/separation = 16
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer" unique_id=1771422232]
+layout_mode = 2
+theme_override_font_sizes/font_size = 32
+text = "Example Level #1"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer" unique_id=1105591262]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=580896160]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 32
+script = ExtResource("2_sycfm")
+
+[node name="LoseButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=1120638699]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Lose"
+
+[node name="WinButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=1019168185]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Win"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=1186288776]
+layout_mode = 2
+
+[node name="TutorialButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=501380068]
+layout_mode = 2
+text = "Tutorial"
+
+[node name="InputDisplayLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=743402689]
+layout_mode = 2
+size_flags_horizontal = 3
+horizontal_alignment = 1
+script = ExtResource("3_pmyid")
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=1165919418]
+layout_mode = 2
+text = "Change Level Color: "
+
+[node name="ColorPickerButton" type="ColorPickerButton" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=2109296008]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Change Color"
+
+[node name="TutorialManager" type="Node" parent="." unique_id=928060640]
+unique_name_in_owner = true
+script = ExtResource("4_bsimd")
+tutorial_scenes = Array[PackedScene]([ExtResource("5_fmabn")])
+
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/LoseButton" to="." method="_on_lose_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/WinButton" to="." method="_on_win_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/TutorialButton" to="." method="_on_tutorial_button_pressed"]
+[connection signal="color_changed" from="MarginContainer/VBoxContainer/HBoxContainer2/ColorPickerButton" to="." method="_on_color_picker_button_color_changed"]
diff --git a/evolve-die-repeat/scenes/game_scene/levels/level_2.tscn b/evolve-die-repeat/scenes/game_scene/levels/level_2.tscn
new file mode 100644
index 0000000..6972d5d
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/levels/level_2.tscn
@@ -0,0 +1,107 @@
+[gd_scene format=3 uid="uid://dok28ptvihsse"]
+
+[ext_resource type="Script" path="res://scenes/game_scene/levels/level.gd" id="1_rgywr"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_8s04l"]
+[ext_resource type="Script" path="res://scenes/game_scene/input_display_label.gd" id="3_kjg0a"]
+[ext_resource type="Script" path="res://scenes/game_scene/tutorial_manager.gd" id="4_w8c7f"]
+[ext_resource type="PackedScene" path="res://scenes/game_scene/tutorials/tutorial_2.tscn" id="5_i1l6d"]
+
+[node name="Level2" type="Control" unique_id=2110036821]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_rgywr")
+next_level_path = "res://scenes/game_scene/levels/level_3.tscn"
+
+[node name="BackgroundColor" type="ColorRect" parent="." unique_id=1479828644]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 1)
+
+[node name="MarginContainer" type="MarginContainer" parent="." unique_id=523912810]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 32
+theme_override_constants/margin_top = 32
+theme_override_constants/margin_right = 32
+theme_override_constants/margin_bottom = 32
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=107158970]
+layout_mode = 2
+theme_override_constants/separation = 16
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer" unique_id=806912222]
+layout_mode = 2
+theme_override_font_sizes/font_size = 32
+text = "Example Level #2"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer" unique_id=1089021380]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=1396072092]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 32
+script = ExtResource("2_8s04l")
+
+[node name="LoseButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=842568930]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Lose"
+
+[node name="WinButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=2033630088]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Win"
+
+[node name="LoseButton2" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=866810661]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Lose"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=429267659]
+layout_mode = 2
+
+[node name="TutorialButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=63948276]
+layout_mode = 2
+text = "Tutorial"
+
+[node name="InputDisplayLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=919863907]
+layout_mode = 2
+size_flags_horizontal = 3
+horizontal_alignment = 1
+script = ExtResource("3_kjg0a")
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=1863562002]
+layout_mode = 2
+text = "Change Level Color: "
+
+[node name="ColorPickerButton" type="ColorPickerButton" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=153143410]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Change Color"
+
+[node name="TutorialManager" type="Node" parent="." unique_id=927712379]
+unique_name_in_owner = true
+script = ExtResource("4_w8c7f")
+tutorial_scenes = Array[PackedScene]([ExtResource("5_i1l6d")])
+
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/LoseButton" to="." method="_on_lose_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/WinButton" to="." method="_on_win_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/LoseButton2" to="." method="_on_lose_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/TutorialButton" to="." method="_on_tutorial_button_pressed"]
+[connection signal="color_changed" from="MarginContainer/VBoxContainer/HBoxContainer2/ColorPickerButton" to="." method="_on_color_picker_button_color_changed"]
diff --git a/evolve-die-repeat/scenes/game_scene/levels/level_3.tscn b/evolve-die-repeat/scenes/game_scene/levels/level_3.tscn
new file mode 100644
index 0000000..868250d
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/levels/level_3.tscn
@@ -0,0 +1,112 @@
+[gd_scene format=3 uid="uid://d4idbbaheqxq7"]
+
+[ext_resource type="Script" path="res://scenes/game_scene/levels/level.gd" id="1_3s8h5"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_satlj"]
+[ext_resource type="Script" path="res://scenes/game_scene/input_display_label.gd" id="3_4popk"]
+[ext_resource type="Script" path="res://scenes/game_scene/tutorial_manager.gd" id="4_mdq82"]
+[ext_resource type="PackedScene" path="res://scenes/game_scene/tutorials/tutorial_3.tscn" id="5_32y40"]
+
+[node name="Level3" type="Control" unique_id=710690354]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_3s8h5")
+
+[node name="BackgroundColor" type="ColorRect" parent="." unique_id=2048289432]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 1)
+
+[node name="MarginContainer" type="MarginContainer" parent="." unique_id=1139802558]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 32
+theme_override_constants/margin_top = 32
+theme_override_constants/margin_right = 32
+theme_override_constants/margin_bottom = 32
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer" unique_id=24784916]
+layout_mode = 2
+theme_override_constants/separation = 16
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer" unique_id=1118917856]
+layout_mode = 2
+theme_override_font_sizes/font_size = 32
+text = "Example Level #3"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer" unique_id=1867660130]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=1499324122]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 32
+script = ExtResource("2_satlj")
+
+[node name="WinButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=500607117]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Win"
+
+[node name="LoseButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=1616502597]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Lose"
+
+[node name="LoseButton2" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=1282863293]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Lose"
+
+[node name="LoseButton3" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer" unique_id=760845382]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Lose"
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer" unique_id=1147500718]
+layout_mode = 2
+
+[node name="TutorialButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=90402611]
+layout_mode = 2
+text = "Tutorial"
+
+[node name="InputDisplayLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=1335603973]
+layout_mode = 2
+size_flags_horizontal = 3
+horizontal_alignment = 1
+script = ExtResource("3_4popk")
+
+[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=1223774581]
+layout_mode = 2
+text = "Change Level Color: "
+
+[node name="ColorPickerButton" type="ColorPickerButton" parent="MarginContainer/VBoxContainer/HBoxContainer2" unique_id=550861861]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Change Color"
+
+[node name="TutorialManager" type="Node" parent="." unique_id=18876677]
+unique_name_in_owner = true
+script = ExtResource("4_mdq82")
+tutorial_scenes = Array[PackedScene]([ExtResource("5_32y40")])
+
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/WinButton" to="." method="_on_win_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/LoseButton" to="." method="_on_lose_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/LoseButton2" to="." method="_on_lose_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer/LoseButton3" to="." method="_on_lose_button_pressed"]
+[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/TutorialButton" to="." method="_on_tutorial_button_pressed"]
+[connection signal="color_changed" from="MarginContainer/VBoxContainer/HBoxContainer2/ColorPickerButton" to="." method="_on_color_picker_button_color_changed"]
diff --git a/evolve-die-repeat/scenes/game_scene/tutorial_manager.gd b/evolve-die-repeat/scenes/game_scene/tutorial_manager.gd
new file mode 100644
index 0000000..4c12e17
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/tutorial_manager.gd
@@ -0,0 +1,30 @@
+extends Node
+## A script to add into a level or game scene to display tutorial windows.
+
+## A list of tutorial scenes to open, one after the other.
+@export var tutorial_scenes : Array[PackedScene]
+## If true, open the tutorials when the scene becomes ready.
+@export var auto_open : bool = false
+## Delay before opening the tutorials when the scene becomes ready.
+@export var auto_open_delay : float = 0.25
+
+func open_tutorials() -> void:
+ var _initial_focus_control : Control = get_viewport().gui_get_focus_owner()
+ for tutorial_scene in tutorial_scenes:
+ var tutorial_menu : Control = tutorial_scene.instantiate()
+ if tutorial_menu == null:
+ push_warning("tutorial failed to open %s" % tutorial_scene)
+ return
+ get_tree().current_scene.call_deferred("add_child", tutorial_menu)
+ if tutorial_menu.has_signal(&"closed"):
+ await tutorial_menu.closed
+ else:
+ await tutorial_menu.tree_exited
+ if is_inside_tree() and _initial_focus_control:
+ _initial_focus_control.grab_focus()
+
+func _ready() -> void:
+ if auto_open:
+ if auto_open_delay > 0.0:
+ await get_tree().create_timer(auto_open_delay, false).timeout
+ open_tutorials.call_deferred()
diff --git a/evolve-die-repeat/scenes/game_scene/tutorial_manager.gd.uid b/evolve-die-repeat/scenes/game_scene/tutorial_manager.gd.uid
new file mode 100644
index 0000000..68ab1df
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/tutorial_manager.gd.uid
@@ -0,0 +1 @@
+uid://bpvusqa2olipb
diff --git a/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_1.tscn b/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_1.tscn
new file mode 100644
index 0000000..83cbde9
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_1.tscn
@@ -0,0 +1,19 @@
+[gd_scene format=3 uid="uid://l2prmgoscdt0"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_b2dkv"]
+
+[node name="OverlaidWindow" unique_id=1876550858 instance=ExtResource("1_b2dkv")]
+custom_minimum_size = Vector2(420, 200)
+update_content = true
+text = "Click the Win button to progress.
+Click the Lose button to try again."
+title = "Tutorial"
+title_font_size = 20
+
+[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(1788474031) index="0" unique_id=1049966061]
+theme_override_font_sizes/font_size = 20
+text = "Tutorial"
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+text = "Click the Win button to progress.
+Click the Lose button to try again."
diff --git a/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_2.tscn b/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_2.tscn
new file mode 100644
index 0000000..48e2298
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_2.tscn
@@ -0,0 +1,19 @@
+[gd_scene format=3 uid="uid://byo4w7vrunyfn"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_sjjon"]
+
+[node name="OverlaidWindow" unique_id=902196267 instance=ExtResource("1_sjjon")]
+custom_minimum_size = Vector2(420, 200)
+update_content = true
+text = "Progress is saved.
+Pressing Continue from the main menu will load at the last checkpoint."
+title = "Tutorial"
+title_font_size = 20
+
+[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(1788474031) index="0" unique_id=1049966061]
+theme_override_font_sizes/font_size = 20
+text = "Tutorial"
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+text = "Progress is saved.
+Pressing Continue from the main menu will load at the last checkpoint."
diff --git a/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_3.tscn b/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_3.tscn
new file mode 100644
index 0000000..3c4dd9f
--- /dev/null
+++ b/evolve-die-repeat/scenes/game_scene/tutorials/tutorial_3.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://cktxovjiylmpi"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_hy5iy"]
+
+[node name="OverlaidWindow" unique_id=1491564368 instance=ExtResource("1_hy5iy")]
+custom_minimum_size = Vector2(420, 200)
+update_content = true
+text = "The color picker at the bottom-right updates the level state. This change persists until the game is reset.
+
+The label at the bottom-center displays the current input action detected, if any are setup for the project."
+title = "Tutorial"
+title_font_size = 20
+
+[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(1788474031) index="0" unique_id=1049966061]
+theme_override_font_sizes/font_size = 20
+text = "Tutorial"
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+text = "The color picker at the bottom-right updates the level state. This change persists until the game is reset.
+
+The label at the bottom-center displays the current input action detected, if any are setup for the project."
diff --git a/evolve-die-repeat/scenes/loading_screen/level_loading_screen.tscn b/evolve-die-repeat/scenes/loading_screen/level_loading_screen.tscn
new file mode 100644
index 0000000..05f4443
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/level_loading_screen.tscn
@@ -0,0 +1,11 @@
+[gd_scene format=3 uid="uid://brhug56ihh06"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.tscn" id="1_akme8"]
+[ext_resource type="Script" path="res://scenes/loading_screen/loading_screen.gd" id="2_kl30i"]
+
+[node name="LevelLoadingScreen" unique_id=1716792690 instance=ExtResource("1_akme8")]
+script = ExtResource("2_kl30i")
+_in_progress = "Loading Level..."
+_in_progress_waiting = "Still Loading Level..."
+_in_progress_still_waiting = "Still Loading Level... (%d seconds)"
+_complete = "Loading Level Complete!"
diff --git a/evolve-die-repeat/scenes/loading_screen/loading_screen.gd b/evolve-die-repeat/scenes/loading_screen/loading_screen.gd
new file mode 100644
index 0000000..c454123
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/loading_screen.gd
@@ -0,0 +1 @@
+extends LoadingScreen
diff --git a/evolve-die-repeat/scenes/loading_screen/loading_screen.gd.uid b/evolve-die-repeat/scenes/loading_screen/loading_screen.gd.uid
new file mode 100644
index 0000000..6611eda
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/loading_screen.gd.uid
@@ -0,0 +1 @@
+uid://ca3k1q1qyu51x
diff --git a/evolve-die-repeat/scenes/loading_screen/loading_screen.tscn b/evolve-die-repeat/scenes/loading_screen/loading_screen.tscn
new file mode 100644
index 0000000..e069834
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/loading_screen.tscn
@@ -0,0 +1,7 @@
+[gd_scene format=3 uid="uid://cj6bp67uxoetc"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.tscn" id="1_a8kcw"]
+[ext_resource type="Script" path="res://scenes/loading_screen/loading_screen.gd" id="2_53rj4"]
+
+[node name="LoadingScreen" unique_id=179842335 instance=ExtResource("1_a8kcw")]
+script = ExtResource("2_53rj4")
diff --git a/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.gd b/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.gd
new file mode 100644
index 0000000..bfbbd8d
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.gd
@@ -0,0 +1,98 @@
+extends LoadingScreen
+## Loading Screen extension that pre-loads shaders before opening the next scene.
+
+## Path to directory with the material shaders that should be pre-loaded.
+@export_dir var _spatial_shader_material_dir : String
+## Path to the scene that should trigger a shader pre-loading.
+@export_file("*.tscn") var _cache_shaders_scene : String
+## Mesh object that the material shaders should be applied to.
+@export var _mesh : Mesh
+@export_group("Advanced")
+## Includes material scenes with extensions that match the strings.
+@export var _matching_extensions : Array[String] = [".tres", ".material", ".res"]
+## Excludes subfolders that match the strings.
+@export var _ignore_subfolders : Array[String] = [".", ".."]
+## Delay between loading each shader onto the mesh.
+@export var _shader_delay_timer : float = 0.1
+
+var _loading_shader_cache : bool = false
+
+var _caching_progress : float = 0.0 :
+ set(value):
+ if value <= _caching_progress:
+ return
+ _caching_progress = value
+ update_total_loading_progress()
+ _reset_loading_stage()
+
+func can_load_shader_cache() -> bool:
+ return not _spatial_shader_material_dir.is_empty() and \
+ not _cache_shaders_scene.is_empty() and \
+ SceneLoader.is_loading_scene(_cache_shaders_scene)
+
+func update_total_loading_progress() -> void:
+ var partial_total := _scene_loading_progress
+ if can_load_shader_cache():
+ partial_total += _caching_progress
+ partial_total /= 2
+ _total_loading_progress = partial_total
+
+func _set_scene_loading_complete() -> void:
+ super._set_scene_loading_complete()
+ if can_load_shader_cache() and not _loading_shader_cache:
+ _loading_shader_cache = true
+ _show_all_draw_passes_once()
+ if can_load_shader_cache() and _caching_progress < 1.0:
+ return
+ SceneLoader._background_loading = false
+ SceneLoader.set_process(true)
+
+func _show_all_draw_passes_once() -> void:
+ var all_materials := _traverse_folders(_spatial_shader_material_dir)
+ var total_material_count := all_materials.size()
+ var cached_material_count := 0
+ for material_path in all_materials:
+ _load_material(material_path)
+ cached_material_count += 1
+ _caching_progress = float(cached_material_count) / total_material_count
+ if _shader_delay_timer > 0:
+ await(get_tree().create_timer(_shader_delay_timer).timeout)
+
+func _traverse_folders(dir_path:String) -> PackedStringArray:
+ var material_list:PackedStringArray = []
+ if not dir_path.ends_with("/"):
+ dir_path += "/"
+ var dir := DirAccess.open(dir_path)
+ if not dir:
+ push_error("failed to access the path ", dir_path)
+ return []
+ if dir.list_dir_begin() != OK:
+ push_error("failed to access the path ", dir_path)
+ return []
+ var file_name := dir.get_next()
+ while file_name != "":
+ if not dir.current_is_dir():
+ var matches : bool = false
+ for extension in _matching_extensions:
+ if file_name.ends_with(extension):
+ matches = true
+ break
+ if matches:
+ material_list.append(dir_path + file_name)
+ else:
+ var subfolder_name := file_name
+ if not subfolder_name in _ignore_subfolders:
+ material_list.append_array(_traverse_folders(dir_path + subfolder_name))
+ file_name = dir.get_next()
+
+ return material_list
+
+func _load_material(path:String) -> void:
+ var material_shower := MeshInstance3D.new()
+ material_shower.mesh = _mesh
+ var material := ResourceLoader.load(path) as Material
+ material_shower.set_surface_override_material(0, material)
+ %SpatialShaderTypeCaches.add_child(material_shower)
+
+func _ready() -> void:
+ SceneLoader._background_loading = true
diff --git a/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.gd.uid b/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.gd.uid
new file mode 100644
index 0000000..7fbdde2
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.gd.uid
@@ -0,0 +1 @@
+uid://cg7lkm5qw226o
diff --git a/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.tscn b/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.tscn
new file mode 100644
index 0000000..b9137e9
--- /dev/null
+++ b/evolve-die-repeat/scenes/loading_screen/loading_screen_with_shader_caching.tscn
@@ -0,0 +1,22 @@
+[gd_scene format=3 uid="uid://26mrq3f5vqql"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/loading_screen/loading_screen.tscn" id="1_alqvd"]
+[ext_resource type="Script" path="res://scenes/loading_screen/loading_screen_with_shader_caching.gd" id="2_n5fdm"]
+
+[sub_resource type="QuadMesh" id="QuadMesh_klnwy"]
+
+[node name="LoadingScreen" unique_id=1866533347 instance=ExtResource("1_alqvd")]
+script = ExtResource("2_n5fdm")
+_spatial_shader_material_dir = ""
+_cache_shaders_scene = "res://scenes/game_scene/game_ui.tscn"
+_mesh = SubResource("QuadMesh_klnwy")
+_matching_extensions = Array[String]([".tres", ".material", ".res"])
+_ignore_subfolders = Array[String]([".", ".."])
+_shader_delay_timer = 0.1
+
+[node name="SpatialShaderTypeCaches" type="Node3D" parent="." index="2" unique_id=634879644]
+unique_name_in_owner = true
+
+[node name="Camera3D" type="Camera3D" parent="SpatialShaderTypeCaches" index="0" unique_id=387206904]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.408)
+current = true
diff --git a/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.gd b/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.gd
new file mode 100644
index 0000000..540b109
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.gd
@@ -0,0 +1,33 @@
+extends Control
+
+## Loads a simple ItemList node within a margin container. SceneLister updates
+## the available scenes in the directory provided. Activating a level will update
+## the GameState's current_level, and emit a signal. The main menu node will trigger
+## a load action from that signal.
+
+signal level_selected
+
+@onready var level_buttons_container: ItemList = %LevelButtonsContainer
+@onready var scene_lister: SceneLister = $SceneLister
+var level_paths : Array[String]
+
+func _ready() -> void:
+ add_levels_to_container()
+
+## A fresh level list is propgated into the ItemList, and the file names are cleaned
+func add_levels_to_container() -> void:
+ level_buttons_container.clear()
+ level_paths.clear()
+ var game_state := GameState.get_or_create_state()
+ for file_path in game_state.level_states.keys():
+ var file_name : String = file_path.get_file() # e.g., "level_1.tscn"
+ file_name = file_name.trim_suffix(".tscn") # Remove the ".tscn" extension
+ file_name = file_name.replace("_", " ") # Replace underscores with spaces
+ file_name = file_name.capitalize() # Convert to proper case
+ var button_name := str(file_name)
+ level_buttons_container.add_item(button_name)
+ level_paths.append(file_path)
+
+func _on_level_buttons_container_item_activated(index: int) -> void:
+ GameState.set_checkpoint_level_path(level_paths[index])
+ level_selected.emit()
diff --git a/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.gd.uid b/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.gd.uid
new file mode 100644
index 0000000..0cdf158
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.gd.uid
@@ -0,0 +1 @@
+uid://b2722vtbsd4q1
diff --git a/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.tscn b/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.tscn
new file mode 100644
index 0000000..c675eff
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/level_select_menu/level_select_menu.tscn
@@ -0,0 +1,49 @@
+[gd_scene format=3 uid="uid://ccab8m5dn7vjx"]
+
+[ext_resource type="Script" path="res://scenes/menus/level_select_menu/level_select_menu.gd" id="1_a0tqu"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_tjb7s"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/extras/scripts/scene_lister.gd" id="3_le4jx"]
+
+[node name="LevelSelectMenu" type="Control" unique_id=1470024191]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_a0tqu")
+
+[node name="Control" type="Control" parent="." unique_id=1541649425]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("2_tjb7s")
+
+[node name="LevelButtonsContainer" type="ItemList" parent="Control" unique_id=17921809]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -200.0
+offset_top = -17.5
+offset_right = 200.0
+offset_bottom = 17.5
+grow_horizontal = 2
+grow_vertical = 2
+auto_height = true
+item_count = 1
+item_0/text = "1 - ExampleLevel"
+
+[node name="SceneLister" type="Node" parent="." unique_id=1851760223]
+script = ExtResource("3_le4jx")
+files = Array[String](["res://scenes/game_scene/levels/level_1.tscn", "res://scenes/game_scene/levels/level_2.tscn", "res://scenes/game_scene/levels/level_3.tscn"])
+directory = "res://scenes/game_scene/levels"
+
+[connection signal="item_activated" from="Control/LevelButtonsContainer" to="." method="_on_level_buttons_container_item_activated"]
diff --git a/evolve-die-repeat/scenes/menus/main_menu/main_menu.gd b/evolve-die-repeat/scenes/menus/main_menu/main_menu.gd
new file mode 100644
index 0000000..d36d69d
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/main_menu/main_menu.gd
@@ -0,0 +1,50 @@
+extends MainMenu
+## Main menu extension that adds options.
+## The scene adds a 'Continue' button if a game is in progress.
+
+## Optional scene to open when the player clicks a 'Level Select' button.
+@export var level_select_packed_scene: PackedScene
+## If true, have the player confirm before starting a new game if a game is in progress.
+@export var confirm_new_game : bool = true
+
+@onready var continue_game_button = %ContinueGameButton
+@onready var level_select_button = %LevelSelectButton
+@onready var new_game_confirmation = %NewGameConfirmation
+
+func load_game_scene() -> void:
+ GameState.start_game()
+ super.load_game_scene()
+
+func new_game() -> void:
+ if confirm_new_game and continue_game_button.visible:
+ new_game_confirmation.show()
+ else:
+ GameState.reset()
+ load_game_scene()
+
+func _add_level_select_if_set() -> void:
+ if level_select_packed_scene == null: return
+ if GameState.get_levels_reached() <= 1 : return
+ level_select_button.show()
+
+func _show_continue_if_set() -> void:
+ if GameState.get_current_level_path().is_empty(): return
+ continue_game_button.show()
+
+func _ready() -> void:
+ super._ready()
+ _add_level_select_if_set()
+ _show_continue_if_set()
+
+func _on_continue_game_button_pressed() -> void:
+ GameState.continue_game()
+ load_game_scene()
+
+func _on_level_select_button_pressed() -> void:
+ var level_select_scene := _open_sub_menu(level_select_packed_scene)
+ if level_select_scene.has_signal("level_selected"):
+ level_select_scene.connect("level_selected", load_game_scene)
+
+func _on_new_game_confirmation_confirmed() -> void:
+ GameState.reset()
+ load_game_scene()
diff --git a/evolve-die-repeat/scenes/menus/main_menu/main_menu.gd.uid b/evolve-die-repeat/scenes/menus/main_menu/main_menu.gd.uid
new file mode 100644
index 0000000..0abe9c3
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/main_menu/main_menu.gd.uid
@@ -0,0 +1 @@
+uid://djw25uvv3uu1f
diff --git a/evolve-die-repeat/scenes/menus/main_menu/main_menu.tscn b/evolve-die-repeat/scenes/menus/main_menu/main_menu.tscn
new file mode 100644
index 0000000..c43f6af
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/main_menu/main_menu.tscn
@@ -0,0 +1,44 @@
+[gd_scene format=3 uid="uid://xci60pu6vaeq"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.tscn" id="1_ub2bi"]
+[ext_resource type="Script" path="res://scenes/menus/main_menu/main_menu.gd" id="2_x31ro"]
+[ext_resource type="PackedScene" path="res://scenes/windows/main_menu_options_window.tscn" id="3_0jpat"]
+[ext_resource type="PackedScene" path="res://scenes/windows/main_menu_credits_window.tscn" id="4_m0ubd"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="5_627ab"]
+
+[node name="MainMenu" unique_id=869232425 instance=ExtResource("1_ub2bi")]
+script = ExtResource("2_x31ro")
+level_select_packed_scene = null
+confirm_new_game = true
+options_packed_scene = ExtResource("3_0jpat")
+credits_packed_scene = ExtResource("4_m0ubd")
+
+[node name="ContinueGameButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" parent_id_path=PackedInt32Array(2017477786) index="1" unique_id=1495566523]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Continue"
+
+[node name="LevelSelectButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" parent_id_path=PackedInt32Array(2017477786) index="2" unique_id=1348895676]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Level Select"
+
+[node name="NewGameConfirmation" parent="." index="5" unique_id=487332143 instance=ExtResource("5_627ab")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 1
+offset_left = -210.0
+offset_top = -100.0
+offset_right = 210.0
+offset_bottom = 100.0
+text = "Are you sure you want to start a new game?
+
+All progress in the current game will be lost."
+title = "New Game"
+
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/ContinueGameButton" to="." method="_on_continue_game_button_pressed"]
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/LevelSelectButton" to="." method="_on_level_select_button_pressed"]
+[connection signal="confirmed" from="NewGameConfirmation" to="." method="_on_new_game_confirmation_confirmed"]
diff --git a/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.gd b/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.gd
new file mode 100644
index 0000000..0dd321c
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.gd
@@ -0,0 +1,80 @@
+extends MainMenu
+## Main menu extension that adds options and animates the title and menu fading in.
+## The scene adds a 'Continue' button if a game is in progress.
+## The animation can be skipped by the player with any input.
+
+## Optional scene to open when the player clicks a 'Level Select' button.
+@export var level_select_packed_scene: PackedScene
+## If true, have the player confirm before starting a new game if a game is in progress.
+@export var confirm_new_game : bool = true
+
+var animation_state_machine : AnimationNodeStateMachinePlayback
+
+@onready var continue_game_button = %ContinueGameButton
+@onready var level_select_button = %LevelSelectButton
+@onready var new_game_confirmation = %NewGameConfirmation
+
+func load_game_scene() -> void:
+ GameState.start_game()
+ super.load_game_scene()
+
+func new_game() -> void:
+ if confirm_new_game and continue_game_button.visible:
+ new_game_confirmation.show()
+ else:
+ GameState.reset()
+ load_game_scene()
+
+func intro_done() -> void:
+ animation_state_machine.travel("OpenMainMenu")
+
+func _is_in_intro() -> bool:
+ return animation_state_machine.get_current_node() == "Intro"
+
+func _event_skips_intro(event : InputEvent) -> bool:
+ return event.is_action_released("ui_accept") or \
+ event.is_action_released("ui_select") or \
+ event.is_action_released("ui_cancel") or \
+ _event_is_mouse_button_released(event)
+
+func _open_sub_menu(menu : PackedScene) -> Node:
+ animation_state_machine.travel("OpenSubMenu")
+ return super._open_sub_menu(menu)
+
+func _close_sub_menu() -> void:
+ super._close_sub_menu()
+ animation_state_machine.travel("OpenMainMenu")
+
+func _input(event : InputEvent) -> void:
+ if _is_in_intro() and _event_skips_intro(event):
+ intro_done()
+ return
+ super._input(event)
+
+func _show_level_select_if_set() -> void:
+ if level_select_packed_scene == null: return
+ if GameState.get_levels_reached() <= 1 : return
+ level_select_button.show()
+
+func _show_continue_if_set() -> void:
+ if GameState.get_current_level_path().is_empty(): return
+ continue_game_button.show()
+
+func _ready() -> void:
+ super._ready()
+ _show_level_select_if_set()
+ _show_continue_if_set()
+ animation_state_machine = $MenuAnimationTree.get("parameters/playback")
+
+func _on_continue_game_button_pressed() -> void:
+ GameState.continue_game()
+ load_game_scene()
+
+func _on_level_select_button_pressed() -> void:
+ var level_select_scene := _open_sub_menu(level_select_packed_scene)
+ if level_select_scene.has_signal("level_selected"):
+ level_select_scene.connect("level_selected", load_game_scene)
+
+func _on_new_game_confirmation_confirmed() -> void:
+ GameState.reset()
+ load_game_scene()
diff --git a/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.gd.uid b/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.gd.uid
new file mode 100644
index 0000000..94b6399
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.gd.uid
@@ -0,0 +1 @@
+uid://6itnsrcgk2wu
diff --git a/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.tscn b/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.tscn
new file mode 100644
index 0000000..94c7bdd
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/main_menu/main_menu_with_animations.tscn
@@ -0,0 +1,385 @@
+[gd_scene format=3 uid="uid://bq4y2g0eurewb"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/main_menu/main_menu.tscn" id="1_sr523"]
+[ext_resource type="Script" path="res://scenes/menus/main_menu/main_menu_with_animations.gd" id="2_ixa71"]
+[ext_resource type="PackedScene" path="res://scenes/windows/main_menu_options_window.tscn" id="3_2k5l7"]
+[ext_resource type="PackedScene" path="res://scenes/windows/main_menu_credits_window.tscn" id="4_vsp5b"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="5_6xh1d"]
+
+[sub_resource type="Animation" id="1"]
+resource_name = "Intro"
+length = 2.4
+tracks/0/type = "method"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath(".")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(2.4),
+"transitions": PackedFloat32Array(1),
+"values": [{
+"args": [],
+"method": &"intro_done"
+}]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("MenuContainer/TitleMargin/TitleContainer:modulate")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0, 0.8),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("MenuContainer/SubTitleMargin/SubTitleContainer:modulate")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0, 0.8, 1.6),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("MenuContainer/MenuButtonsMargin/MenuButtonsContainer:modulate")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0, 1.6, 2.4),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("MouseFilter:mouse_filter")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0, 2.4),
+"transitions": PackedFloat32Array(1, 1),
+"update": 1,
+"values": [0, 2]
+}
+tracks/5/type = "value"
+tracks/5/imported = false
+tracks/5/enabled = true
+tracks/5/path = NodePath("VersionMargin/VersionContainer:modulate")
+tracks/5/interp = 1
+tracks/5/loop_wrap = true
+tracks/5/keys = {
+"times": PackedFloat32Array(0, 1.6, 2.4),
+"transitions": PackedFloat32Array(1, 1, 1),
+"update": 0,
+"values": [Color(1, 1, 1, 0), Color(1, 1, 1, 0), Color(1, 1, 1, 1)]
+}
+
+[sub_resource type="Animation" id="6"]
+resource_name = "OpenMainMenu"
+length = 0.1
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("MenuContainer/TitleMargin/TitleContainer:modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("MenuContainer/SubTitleMargin/SubTitleContainer:modulate")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("MenuContainer/MenuButtonsMargin/MenuButtonsContainer:modulate")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("MouseFilter:mouse_filter")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [2]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("MenuContainer:modulate")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/5/type = "value"
+tracks/5/imported = false
+tracks/5/enabled = true
+tracks/5/path = NodePath("VersionMargin/VersionContainer:modulate")
+tracks/5/interp = 1
+tracks/5/loop_wrap = true
+tracks/5/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/6/type = "value"
+tracks/6/imported = false
+tracks/6/enabled = true
+tracks/6/path = NodePath("MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer:lock")
+tracks/6/interp = 1
+tracks/6/loop_wrap = true
+tracks/6/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [false]
+}
+
+[sub_resource type="Animation" id="4"]
+resource_name = "OpenSubMenu"
+length = 0.2
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("MenuContainer:modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 0)]
+}
+
+[sub_resource type="Animation" id="2"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("MenuContainer/TitleMargin/TitleContainer:modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 0)]
+}
+tracks/1/type = "value"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("MenuContainer/SubTitleMargin/SubTitleContainer:modulate")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 0)]
+}
+tracks/2/type = "value"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("MenuContainer/MenuButtonsMargin/MenuButtonsContainer:modulate")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 0)]
+}
+tracks/3/type = "value"
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/path = NodePath("MouseFilter:mouse_filter")
+tracks/3/interp = 1
+tracks/3/loop_wrap = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [2]
+}
+tracks/4/type = "value"
+tracks/4/imported = false
+tracks/4/enabled = true
+tracks/4/path = NodePath("MenuContainer:modulate")
+tracks/4/interp = 1
+tracks/4/loop_wrap = true
+tracks/4/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/5/type = "value"
+tracks/5/imported = false
+tracks/5/enabled = true
+tracks/5/path = NodePath("VersionMargin/VersionContainer:modulate")
+tracks/5/interp = 1
+tracks/5/loop_wrap = true
+tracks/5/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 0)]
+}
+tracks/6/type = "value"
+tracks/6/imported = false
+tracks/6/enabled = true
+tracks/6/path = NodePath("MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer:lock")
+tracks/6/interp = 1
+tracks/6/loop_wrap = true
+tracks/6/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 1,
+"values": [true]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_2kqig"]
+_data = {
+&"Intro": SubResource("1"),
+&"OpenMainMenu": SubResource("6"),
+&"OpenSubMenu": SubResource("4"),
+&"RESET": SubResource("2")
+}
+
+[sub_resource type="AnimationNodeAnimation" id="7"]
+animation = &"Intro"
+
+[sub_resource type="AnimationNodeAnimation" id="10"]
+animation = &"OpenMainMenu"
+
+[sub_resource type="AnimationNodeAnimation" id="13"]
+animation = &"OpenSubMenu"
+
+[sub_resource type="AnimationNodeStateMachineTransition" id="11"]
+advance_condition = &"intro_done"
+
+[sub_resource type="AnimationNodeStateMachineTransition" id="14"]
+
+[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_j0orr"]
+advance_mode = 2
+
+[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_63dxc"]
+
+[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_vikuh"]
+states/End/position = Vector2(958, 123)
+states/Intro/node = SubResource("7")
+states/Intro/position = Vector2(259, 123)
+states/OpenMainMenu/node = SubResource("10")
+states/OpenMainMenu/position = Vector2(472, 123)
+states/OpenSubMenu/node = SubResource("13")
+states/OpenSubMenu/position = Vector2(734, 123)
+states/Start/position = Vector2(82, 123)
+transitions = ["Intro", "OpenMainMenu", SubResource("11"), "OpenMainMenu", "OpenSubMenu", SubResource("14"), "Start", "Intro", SubResource("AnimationNodeStateMachineTransition_j0orr"), "OpenSubMenu", "OpenMainMenu", SubResource("AnimationNodeStateMachineTransition_63dxc")]
+graph_offset = Vector2(-180.277, 49)
+
+[node name="MainMenu" unique_id=589811630 instance=ExtResource("1_sr523")]
+script = ExtResource("2_ixa71")
+level_select_packed_scene = null
+confirm_new_game = true
+options_packed_scene = ExtResource("3_2k5l7")
+credits_packed_scene = ExtResource("4_vsp5b")
+
+[node name="MenuAnimationPlayer" type="AnimationPlayer" parent="." index="1" unique_id=1492532975]
+libraries/ = SubResource("AnimationLibrary_2kqig")
+
+[node name="MenuAnimationTree" type="AnimationTree" parent="." index="2" unique_id=1630488919]
+tree_root = SubResource("AnimationNodeStateMachine_vikuh")
+anim_player = NodePath("../MenuAnimationPlayer")
+parameters/conditions/intro_done = false
+
+[node name="TitleContainer" parent="MenuContainer/TitleMargin" parent_id_path=PackedInt32Array(2112060331) index="0" unique_id=556427956]
+modulate = Color(1, 1, 1, 0)
+
+[node name="SubTitleContainer" parent="MenuContainer/SubTitleMargin" parent_id_path=PackedInt32Array(1510335937) index="0" unique_id=930520368]
+modulate = Color(1, 1, 1, 0)
+
+[node name="MenuButtonsContainer" parent="MenuContainer/MenuButtonsMargin" parent_id_path=PackedInt32Array(210553886) index="0" unique_id=116448484]
+modulate = Color(1, 1, 1, 0)
+
+[node name="MenuButtonsBoxContainer" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer" index="0" unique_id=2017477786]
+lock = true
+
+[node name="ContinueGameButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" index="1" unique_id=1206401873]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Continue"
+
+[node name="LevelSelectButton" type="Button" parent="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer" index="2" unique_id=78300098]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Level Select"
+
+[node name="VersionContainer" parent="VersionMargin" parent_id_path=PackedInt32Array(1732528471) index="0" unique_id=1603969262]
+modulate = Color(1, 1, 1, 0)
+
+[node name="NewGameConfirmation" parent="." index="7" unique_id=1747133399 instance=ExtResource("5_6xh1d")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(420, 200)
+layout_mode = 1
+offset_left = -210.0
+offset_top = -100.0
+offset_right = 210.0
+offset_bottom = 100.0
+text = "Are you sure you want to start a new game?
+
+All progress in the current game will be lost."
+title = "New Game"
+
+[node name="MouseFilter" type="Control" parent="." index="8" unique_id=33285267]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+metadata/_edit_lock_ = true
+
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/ContinueGameButton" to="." method="_on_continue_game_button_pressed"]
+[connection signal="pressed" from="MenuContainer/MenuButtonsMargin/MenuButtonsContainer/MenuButtonsBoxContainer/LevelSelectButton" to="." method="_on_level_select_button_pressed"]
+[connection signal="confirmed" from="NewGameConfirmation" to="." method="_on_new_game_confirmation_confirmed"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.gd b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.gd
new file mode 100644
index 0000000..629bdb7
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.gd
@@ -0,0 +1,39 @@
+@tool
+extends ListOptionControl
+
+func _set_input_device() -> void:
+ var current_setting : Variant = _get_setting(default_value)
+ if current_setting is bool:
+ current_setting = &"Default"
+ AudioServer.input_device = _get_setting(default_value)
+
+func _add_microphone_audio_stream() -> void:
+ var instance := AudioStreamPlayer.new()
+ instance.stream = AudioStreamMicrophone.new()
+ instance.autoplay = true
+ add_child.call_deferred(instance)
+ instance.ready.connect(_set_input_device)
+
+func _ready() -> void:
+ if ProjectSettings.get_setting("audio/driver/enable_input", false):
+ show()
+ if AudioServer.input_device.is_empty():
+ _add_microphone_audio_stream()
+ else:
+ _set_input_device()
+ if not Engine.is_editor_hint():
+ option_values = AudioServer.get_input_device_list()
+ else:
+ hide()
+ super._ready()
+
+func _on_setting_changed(value : Variant) -> void:
+ if value >= option_values.size(): return
+ AudioServer.input_device = option_values[value]
+ super._on_setting_changed(value)
+
+func _value_title_map(value : Variant) -> String:
+ if value is String:
+ return value
+ else:
+ return super._value_title_map(value)
diff --git a/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.gd.uid b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.gd.uid
new file mode 100644
index 0000000..78eb682
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.gd.uid
@@ -0,0 +1 @@
+uid://h71jrpe7r1cx
diff --git a/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.tscn b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.tscn
new file mode 100644
index 0000000..1224e4c
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_input_option_control.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://7hqbc80e06sy"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn" id="1_lfo7w"]
+[ext_resource type="Script" path="res://scenes/menus/options_menu/audio/audio_input_option_control.gd" id="2_m6bxx"]
+
+[node name="AudioInputOptionControl" unique_id=1866818783 instance=ExtResource("1_lfo7w")]
+visible = false
+script = ExtResource("2_m6bxx")
+option_name = "Input Device"
+option_section = 2
+key = "InputDevice"
+section = "AudioSettings"
+property_type = 4
+
+[node name="OptionLabel" parent="." index="0" unique_id=1854788461]
+text = "Input Device :"
+
+[node name="OptionButton" parent="." index="1" unique_id=264509485]
+size_flags_horizontal = 3
+text_overrun_behavior = 1
+clip_text = true
diff --git a/evolve-die-repeat/scenes/menus/options_menu/audio/audio_options_menu.tscn b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_options_menu.tscn
new file mode 100644
index 0000000..7cf1cf2
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/audio/audio_options_menu.tscn
@@ -0,0 +1,9 @@
+[gd_scene format=3 uid="uid://b5sx6r7ohjrj"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/audio/audio_options_menu.tscn" id="1_33wdu"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/audio/audio_input_option_control.tscn" id="2_vnecy"]
+
+[node name="Audio" unique_id=651805888 instance=ExtResource("1_33wdu")]
+
+[node name="AudioInputOptionControl" parent="VBoxContainer" parent_id_path=PackedInt32Array(1265848630) index="2" unique_id=774781052 instance=ExtResource("2_vnecy")]
+layout_mode = 2
diff --git a/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.gd b/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.gd
new file mode 100644
index 0000000..d3a5f07
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.gd
@@ -0,0 +1,4 @@
+extends Control
+
+func _on_ResetGameControl_reset_confirmed() -> void:
+ GameState.reset()
diff --git a/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.gd.uid b/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.gd.uid
new file mode 100644
index 0000000..4832df8
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.gd.uid
@@ -0,0 +1 @@
+uid://uqwakd86xudk
diff --git a/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.tscn b/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.tscn
new file mode 100644
index 0000000..35a0b69
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/game/game_options_menu.tscn
@@ -0,0 +1,26 @@
+[gd_scene format=3 uid="uid://1vbfefiimyns"]
+
+[ext_resource type="Script" path="res://scenes/menus/options_menu/game/game_options_menu.gd" id="1_hvcyg"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="2_t8t6j"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/game/reset_game_control/reset_game_control.tscn" id="3_pfgxr"]
+
+[node name="Game" type="MarginContainer" unique_id=205916118]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+theme_override_constants/margin_top = 24
+theme_override_constants/margin_bottom = 24
+script = ExtResource("1_hvcyg")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=93516622]
+custom_minimum_size = Vector2(400, 0)
+layout_mode = 2
+size_flags_horizontal = 4
+alignment = 1
+script = ExtResource("2_t8t6j")
+search_depth = 2
+
+[node name="ResetGameControl" parent="VBoxContainer" unique_id=208719318 instance=ExtResource("3_pfgxr")]
+layout_mode = 2
+
+[connection signal="reset_confirmed" from="VBoxContainer/ResetGameControl" to="." method="_on_ResetGameControl_reset_confirmed"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd b/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd
new file mode 100644
index 0000000..3a637c8
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd
@@ -0,0 +1,25 @@
+extends HBoxContainer
+
+const RESET_STRING := "Reset Game:"
+const CONFIRM_STRING := "Confirm Reset:"
+
+signal reset_confirmed
+
+func _on_cancel_button_pressed():
+ %CancelButton.hide()
+ %ConfirmButton.hide()
+ %ResetButton.show()
+ %ResetButton.grab_focus()
+ %ResetLabel.text = RESET_STRING
+
+func _on_reset_button_pressed():
+ %CancelButton.show()
+ %ConfirmButton.show()
+ %CancelButton.grab_focus()
+ %ResetButton.hide()
+ %ResetLabel.text = CONFIRM_STRING
+
+func _on_confirm_button_pressed():
+ reset_confirmed.emit()
+ get_tree().paused = false
+ SceneLoader.reload_current_scene()
diff --git a/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd.uid b/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd.uid
new file mode 100644
index 0000000..59c8056
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd.uid
@@ -0,0 +1 @@
+uid://dnk3oomyk374q
diff --git a/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.tscn b/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.tscn
new file mode 100644
index 0000000..b08e4f1
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/game/reset_game_control/reset_game_control.tscn
@@ -0,0 +1,40 @@
+[gd_scene format=3 uid="uid://jam2ds44byob"]
+
+[ext_resource type="Script" path="res://scenes/menus/options_menu/game/reset_game_control/reset_game_control.gd" id="1_mkb8o"]
+
+[node name="ResetGameControl" type="HBoxContainer" unique_id=1147095030]
+custom_minimum_size = Vector2(0, 32)
+offset_top = 210.0
+offset_right = 305.0
+offset_bottom = 242.0
+script = ExtResource("1_mkb8o")
+
+[node name="ResetLabel" type="Label" parent="." unique_id=666807423]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Reset Game :"
+
+[node name="ResetButton" type="Button" parent="." unique_id=846056487]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(72, 32)
+layout_mode = 2
+text = "Reset"
+
+[node name="CancelButton" type="Button" parent="." unique_id=130771510]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(72, 32)
+layout_mode = 2
+text = "No"
+
+[node name="ConfirmButton" type="Button" parent="." unique_id=963578876]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(72, 32)
+layout_mode = 2
+text = "Yes"
+
+[connection signal="pressed" from="ResetButton" to="." method="_on_reset_button_pressed"]
+[connection signal="pressed" from="CancelButton" to="." method="_on_cancel_button_pressed"]
+[connection signal="pressed" from="ConfirmButton" to="." method="_on_confirm_button_pressed"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/input/input_extras_menu.tscn b/evolve-die-repeat/scenes/menus/options_menu/input/input_extras_menu.tscn
new file mode 100644
index 0000000..2200306
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/input/input_extras_menu.tscn
@@ -0,0 +1,65 @@
+[gd_scene format=3 uid="uid://bp41gchfamu7y"]
+
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="1_1v5x4"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn" id="2_goqr1"]
+
+[node name="Inputs" type="MarginContainer" unique_id=998049297]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1869351161]
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+theme_override_constants/separation = 8
+script = ExtResource("1_1v5x4")
+search_depth = 5
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer" unique_id=561522128]
+layout_mode = 2
+theme_override_constants/margin_top = 32
+theme_override_constants/margin_bottom = 32
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer" unique_id=796157829]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 8
+alignment = 1
+
+[node name="MouseSensitivityControl" parent="VBoxContainer/MarginContainer/VBoxContainer" unique_id=1921387610 instance=ExtResource("2_goqr1")]
+layout_mode = 2
+option_name = "Mouse Sensitivity"
+option_section = 1
+key = "MouseSensitivity"
+section = "InputSettings"
+
+[node name="OptionLabel" parent="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl" index="0" unique_id=1854788461]
+text = "Mouse Sensitivity :"
+
+[node name="HSlider" parent="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl" index="1" unique_id=424108384]
+min_value = 0.25
+max_value = 2.0
+tick_count = 8
+
+[node name="JoypadSensitivityControl" parent="VBoxContainer/MarginContainer/VBoxContainer" unique_id=256777414 instance=ExtResource("2_goqr1")]
+layout_mode = 2
+option_name = "Joypad Sensitivity"
+option_section = 1
+key = "JoypadSensitivity"
+section = "InputSettings"
+
+[node name="OptionLabel" parent="VBoxContainer/MarginContainer/VBoxContainer/JoypadSensitivityControl" index="0" unique_id=1854788461]
+text = "Joypad Sensitivity :"
+
+[node name="HSlider" parent="VBoxContainer/MarginContainer/VBoxContainer/JoypadSensitivityControl" index="1" unique_id=424108384]
+min_value = 0.25
+max_value = 2.0
+tick_count = 8
+
+[editable path="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl"]
+[editable path="VBoxContainer/MarginContainer/VBoxContainer/JoypadSensitivityControl"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/input/input_icon_mapper.tscn b/evolve-die-repeat/scenes/menus/options_menu/input/input_icon_mapper.tscn
new file mode 100644
index 0000000..3fd8e43
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/input/input_icon_mapper.tscn
@@ -0,0 +1,5 @@
+[gd_scene format=3 uid="uid://clsvhv4a67jt2"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_icon_mapper.tscn" id="1_7vieq"]
+
+[node name="InputIconMapper" unique_id=2136107125 instance=ExtResource("1_7vieq")]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/input/input_options_menu.tscn b/evolve-die-repeat/scenes/menus/options_menu/input/input_options_menu.tscn
new file mode 100644
index 0000000..c9fd92a
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/input/input_options_menu.tscn
@@ -0,0 +1,14 @@
+[gd_scene format=3 uid="uid://bke7evcujycfq"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.tscn" id="1_i27v5"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/input/input_icon_mapper.tscn" id="2_hyv0n"]
+
+[node name="Controls" unique_id=294191899 instance=ExtResource("1_i27v5")]
+
+[node name="InputActionsList" parent="VBoxContainer/InputMappingContainer" parent_id_path=PackedInt32Array(1004992485) index="1" unique_id=2089976301 node_paths=PackedStringArray("input_icon_mapper")]
+input_icon_mapper = NodePath("../../../InputIconMapper")
+
+[node name="InputActionsTree" parent="VBoxContainer/InputMappingContainer" parent_id_path=PackedInt32Array(1004992485) index="2" unique_id=780755880 node_paths=PackedStringArray("input_icon_mapper")]
+input_icon_mapper = NodePath("../../../InputIconMapper")
+
+[node name="InputIconMapper" parent="." index="6" unique_id=1326793573 instance=ExtResource("2_hyv0n")]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/input/input_options_menu_with_mouse_sensitivity.tscn b/evolve-die-repeat/scenes/menus/options_menu/input/input_options_menu_with_mouse_sensitivity.tscn
new file mode 100644
index 0000000..17a8833
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/input/input_options_menu_with_mouse_sensitivity.tscn
@@ -0,0 +1,39 @@
+[gd_scene format=3 uid="uid://b5fr8lxqhfu2s"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/input/input_options_menu.tscn" id="1_fwapm"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn" id="2_56ptf"]
+
+[node name="Controls" unique_id=1972124152 instance=ExtResource("1_fwapm")]
+
+[node name="VBoxContainer" parent="." index="0" unique_id=539297323]
+theme_override_constants/separation = 16
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer" index="0" unique_id=1399429015]
+layout_mode = 2
+theme_override_constants/margin_top = 32
+theme_override_constants/margin_bottom = 32
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/MarginContainer" index="0" unique_id=951107081]
+layout_mode = 2
+size_flags_vertical = 3
+alignment = 1
+
+[node name="MouseSensitivityControl" parent="VBoxContainer/MarginContainer/VBoxContainer" index="0" unique_id=1384087 instance=ExtResource("2_56ptf")]
+layout_mode = 2
+option_name = "Mouse Sensitivity"
+option_section = 1
+key = "MouseSensitivity"
+section = "InputSettings"
+
+[node name="OptionLabel" parent="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl" index="0" unique_id=1854788461]
+text = "Mouse Sensitivity :"
+
+[node name="HSlider" parent="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl" index="1" unique_id=424108384]
+min_value = 0.25
+max_value = 2.0
+tick_count = 8
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer" index="1" unique_id=714626530]
+layout_mode = 2
+
+[editable path="VBoxContainer/MarginContainer/VBoxContainer/MouseSensitivityControl"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/master_options_menu_with_tabs.tscn b/evolve-die-repeat/scenes/menus/options_menu/master_options_menu_with_tabs.tscn
new file mode 100644
index 0000000..b47ea9d
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/master_options_menu_with_tabs.tscn
@@ -0,0 +1,42 @@
+[gd_scene format=3 uid="uid://dokae2ohhidmw"]
+
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/paginated_tab_container.gd" id="1_fqpxk"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/input/input_options_menu.tscn" id="2_w1dvr"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/input/input_extras_menu.tscn" id="3_bn2s3"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/audio/audio_options_menu.tscn" id="4_6wkjv"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/video/video_options_menu_with_extras.tscn" id="5_kr6xa"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/game/game_options_menu.tscn" id="6_n2uhm"]
+
+[node name="MasterOptionsMenu" type="TabContainer" unique_id=1111795112]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+tab_alignment = 1
+current_tab = 0
+script = ExtResource("1_fqpxk")
+
+[node name="Controls" parent="." unique_id=1827648101 instance=ExtResource("2_w1dvr")]
+layout_mode = 2
+metadata/_tab_index = 0
+
+[node name="Inputs" parent="." unique_id=1534818691 instance=ExtResource("3_bn2s3")]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 1
+
+[node name="Audio" parent="." unique_id=506115522 instance=ExtResource("4_6wkjv")]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 2
+
+[node name="Video" parent="." unique_id=382045299 instance=ExtResource("5_kr6xa")]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 3
+
+[node name="Game" parent="." unique_id=1395408076 instance=ExtResource("6_n2uhm")]
+visible = false
+layout_mode = 2
+metadata/_tab_index = 4
diff --git a/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu.tscn b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu.tscn
new file mode 100644
index 0000000..2402389
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu.tscn
@@ -0,0 +1,51 @@
+[gd_scene format=3 uid="uid://b08lr2rh336kn"]
+
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd" id="1_w3x3l"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn" id="2_7t2pa"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="3_x0e25"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_xg1r4"]
+
+[node name="MiniOptionsMenu" type="VBoxContainer" unique_id=2109470311]
+custom_minimum_size = Vector2(400, 260)
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -200.0
+offset_top = -130.0
+offset_right = 200.0
+offset_bottom = 130.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 4
+theme_override_constants/separation = 8
+alignment = 1
+script = ExtResource("1_w3x3l")
+audio_control_scene = ExtResource("2_7t2pa")
+
+[node name="AudioControlContainer" type="VBoxContainer" parent="." unique_id=2081802894]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/separation = 8
+script = ExtResource("3_x0e25")
+search_depth = 2
+
+[node name="MuteControl" parent="." unique_id=1533689152 instance=ExtResource("4_xg1r4")]
+unique_name_in_owner = true
+layout_mode = 2
+option_name = "Mute"
+option_section = 2
+key = "Mute"
+section = "AudioSettings"
+
+[node name="FullscreenControl" parent="." unique_id=311912129 instance=ExtResource("4_xg1r4")]
+unique_name_in_owner = true
+layout_mode = 2
+option_name = "Fullscreen"
+option_section = 3
+key = "FullscreenEnabled"
+section = "VideoSettings"
+
+[connection signal="setting_changed" from="MuteControl" to="." method="_on_mute_control_setting_changed"]
+[connection signal="setting_changed" from="FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.gd b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.gd
new file mode 100644
index 0000000..b3bf1fa
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.gd
@@ -0,0 +1,4 @@
+extends "res://addons/maaacks_game_template/base/nodes/menus/options_menu/mini_options_menu.gd"
+
+func _on_reset_game_control_reset_confirmed() -> void:
+ GameState.reset()
diff --git a/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.gd.uid b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.gd.uid
new file mode 100644
index 0000000..c6c2183
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.gd.uid
@@ -0,0 +1 @@
+uid://s0fjtumwwnu4
diff --git a/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.tscn b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.tscn
new file mode 100644
index 0000000..bc40806
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/mini_options_menu_with_reset.tscn
@@ -0,0 +1,56 @@
+[gd_scene format=3 uid="uid://chjqs8p4fglv4"]
+
+[ext_resource type="Script" path="res://scenes/menus/options_menu/mini_options_menu_with_reset.gd" id="1_kj0d4"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/slider_option_control.tscn" id="2_kdjme"]
+[ext_resource type="Script" path="res://addons/maaacks_game_template/base/nodes/utilities/capture_focus.gd" id="3_8p5p7"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/toggle_option_control.tscn" id="4_iq5ao"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/game/reset_game_control/reset_game_control.tscn" id="5_bvuwx"]
+
+[node name="MiniOptionsMenu" type="VBoxContainer" unique_id=1724615502]
+custom_minimum_size = Vector2(400, 260)
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -200.0
+offset_top = -130.0
+offset_right = 200.0
+offset_bottom = 130.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 4
+theme_override_constants/separation = 8
+alignment = 1
+script = ExtResource("1_kj0d4")
+audio_control_scene = ExtResource("2_kdjme")
+
+[node name="AudioControlContainer" type="VBoxContainer" parent="." unique_id=803318905]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/separation = 8
+script = ExtResource("3_8p5p7")
+search_depth = 2
+
+[node name="MuteControl" parent="." unique_id=1252265559 instance=ExtResource("4_iq5ao")]
+unique_name_in_owner = true
+layout_mode = 2
+option_name = "Mute"
+option_section = 2
+key = "Mute"
+section = "AudioSettings"
+
+[node name="FullscreenControl" parent="." unique_id=836716545 instance=ExtResource("4_iq5ao")]
+unique_name_in_owner = true
+layout_mode = 2
+option_name = "Fullscreen"
+option_section = 3
+key = "FullscreenEnabled"
+section = "VideoSettings"
+
+[node name="ResetGameControl" parent="." unique_id=919045811 instance=ExtResource("5_bvuwx")]
+layout_mode = 2
+
+[connection signal="setting_changed" from="MuteControl" to="." method="_on_mute_control_setting_changed"]
+[connection signal="setting_changed" from="FullscreenControl" to="." method="_on_fullscreen_control_setting_changed"]
+[connection signal="reset_confirmed" from="ResetGameControl" to="." method="_on_reset_game_control_reset_confirmed"]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/video/video_options_menu.tscn b/evolve-die-repeat/scenes/menus/options_menu/video/video_options_menu.tscn
new file mode 100644
index 0000000..991fe8d
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/video/video_options_menu.tscn
@@ -0,0 +1,5 @@
+[gd_scene format=3 uid="uid://c3e0iq6i1of70"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.tscn" id="1_ytjyv"]
+
+[node name="Video" unique_id=1641772648 instance=ExtResource("1_ytjyv")]
diff --git a/evolve-die-repeat/scenes/menus/options_menu/video/video_options_menu_with_extras.tscn b/evolve-die-repeat/scenes/menus/options_menu/video/video_options_menu_with_extras.tscn
new file mode 100644
index 0000000..ab21561
--- /dev/null
+++ b/evolve-die-repeat/scenes/menus/options_menu/video/video_options_menu_with_extras.tscn
@@ -0,0 +1,31 @@
+[gd_scene format=3 uid="uid://ctfdv3navnuv8"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/video/video_options_menu.tscn" id="1_n1b6r"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/menus/options_menu/option_control/list_option_control.tscn" id="2_r8snp"]
+
+[node name="Video" unique_id=2066056515 instance=ExtResource("1_n1b6r")]
+
+[node name="AntiAliasingControl" parent="VBoxContainer" parent_id_path=PackedInt32Array(1251699420) index="3" unique_id=1035813408 instance=ExtResource("2_r8snp")]
+layout_mode = 2
+lock_titles = true
+option_values = [0, 1, 2, 3]
+option_titles = Array[String](["Disabled (Fastest)", "2x", "4x", "8x (Slowest)"])
+option_name = "Anti-Aliasing"
+option_section = 3
+key = "Anti-aliasing"
+section = "VideoSettings"
+property_type = 2
+default_value = 0
+
+[node name="CameraShakeControl" parent="VBoxContainer" parent_id_path=PackedInt32Array(1251699420) index="4" unique_id=683521251 instance=ExtResource("2_r8snp")]
+visible = false
+layout_mode = 2
+lock_titles = true
+option_values = [1.0, 0.75, 0.5, 0.0]
+option_titles = Array[String](["Normal", "Reduced", "Minimal", "None"])
+option_name = "Camera Shake"
+option_section = 3
+key = "CameraShake"
+section = "VideoSettings"
+property_type = 3
+default_value = 1.0
diff --git a/evolve-die-repeat/scenes/opening/opening.tscn b/evolve-die-repeat/scenes/opening/opening.tscn
new file mode 100644
index 0000000..889d34d
--- /dev/null
+++ b/evolve-die-repeat/scenes/opening/opening.tscn
@@ -0,0 +1,13 @@
+[gd_scene format=3 uid="uid://5pyqca1inqp5"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/opening/opening.tscn" id="1_ke3tw"]
+[ext_resource type="Texture2D" path="res://assets/godot_engine_logo/logo_vertical_color_dark.png" id="2_yhkvs"]
+
+[node name="Opening" unique_id=1930105641 instance=ExtResource("1_ke3tw")]
+images = Array[Texture2D]([ExtResource("2_yhkvs")])
+
+[node name="ImagesContainer" parent="." index="1" unique_id=1471559661]
+theme_override_constants/margin_left = 64
+theme_override_constants/margin_top = 64
+theme_override_constants/margin_right = 64
+theme_override_constants/margin_bottom = 64
diff --git a/evolve-die-repeat/scenes/windows/game_won_window.gd b/evolve-die-repeat/scenes/windows/game_won_window.gd
new file mode 100644
index 0000000..21707a7
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/game_won_window.gd
@@ -0,0 +1,26 @@
+@tool
+extends OverlaidWindow
+
+signal continue_pressed
+signal main_menu_pressed
+
+func _ready():
+ if OS.has_feature("web"):
+ %ExitButton.hide()
+
+func _on_exit_button_pressed():
+ %ExitConfirmation.show()
+
+func _on_main_menu_button_pressed():
+ %MainMenuConfirmation.show()
+
+func _on_close_button_pressed():
+ continue_pressed.emit()
+ close()
+
+func _on_main_menu_confirmation_confirmed():
+ main_menu_pressed.emit()
+ close()
+
+func _on_exit_confirmation_confirmed():
+ get_tree().quit()
diff --git a/evolve-die-repeat/scenes/windows/game_won_window.gd.uid b/evolve-die-repeat/scenes/windows/game_won_window.gd.uid
new file mode 100644
index 0000000..6756574
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/game_won_window.gd.uid
@@ -0,0 +1 @@
+uid://dsfwkcr0yyfkg
diff --git a/evolve-die-repeat/scenes/windows/game_won_window.tscn b/evolve-die-repeat/scenes/windows/game_won_window.tscn
new file mode 100644
index 0000000..b8ad931
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/game_won_window.tscn
@@ -0,0 +1,60 @@
+[gd_scene format=3 uid="uid://dv2hmqhj41nmr"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_lpba5"]
+[ext_resource type="Script" path="res://scenes/windows/game_won_window.gd" id="2_rvsdo"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="3_ga0ue"]
+
+[node name="GameWonWindow" unique_id=862385343 instance=ExtResource("1_lpba5")]
+custom_minimum_size = Vector2(432, 240)
+script = ExtResource("2_rvsdo")
+update_content = true
+text = "You won!"
+close_button_text = "Continue"
+title_visible = false
+
+[node name="TitleMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="0" unique_id=1262022916]
+visible = false
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+text = "You won!"
+
+[node name="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" parent_id_path=PackedInt32Array(1413292752) index="0" unique_id=1371114575]
+vertical = false
+
+[node name="ExitButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0" unique_id=809131362]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Exit"
+
+[node name="MainMenuButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1" unique_id=1169131466]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Main Menu"
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="2" unique_id=314526102]
+size_flags_horizontal = 3
+text = "Continue"
+
+[node name="MainMenuConfirmation" parent="." index="1" unique_id=1489862545 instance=ExtResource("3_ga0ue")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(220, 0)
+layout_mode = 2
+text = "Exit to the main menu?"
+title = "Confirm Exit"
+
+[node name="ExitConfirmation" parent="." index="2" unique_id=1941427183 instance=ExtResource("3_ga0ue")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Exit the game?"
+title = "Confirm Exit"
+
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ExitButton" to="." method="_on_exit_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/MainMenuButton" to="." method="_on_main_menu_button_pressed"]
+[connection signal="confirmed" from="MainMenuConfirmation" to="." method="_on_main_menu_confirmation_confirmed"]
+[connection signal="confirmed" from="ExitConfirmation" to="." method="_on_exit_confirmation_confirmed"]
diff --git a/evolve-die-repeat/scenes/windows/level_lost_window.gd b/evolve-die-repeat/scenes/windows/level_lost_window.gd
new file mode 100644
index 0000000..7405ea8
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/level_lost_window.gd
@@ -0,0 +1,26 @@
+@tool
+extends OverlaidWindow
+
+signal restart_pressed
+signal main_menu_pressed
+
+func _ready():
+ if OS.has_feature("web"):
+ %ExitButton.hide()
+
+func _on_exit_button_pressed():
+ %ExitConfirmation.show()
+
+func _on_main_menu_button_pressed():
+ %MainMenuConfirmation.show()
+
+func _on_close_button_pressed():
+ restart_pressed.emit()
+ close()
+
+func _on_main_menu_confirmation_confirmed():
+ main_menu_pressed.emit()
+ close()
+
+func _on_exit_confirmation_confirmed():
+ get_tree().quit()
diff --git a/evolve-die-repeat/scenes/windows/level_lost_window.gd.uid b/evolve-die-repeat/scenes/windows/level_lost_window.gd.uid
new file mode 100644
index 0000000..069cc27
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/level_lost_window.gd.uid
@@ -0,0 +1 @@
+uid://70x32kal3kpt
diff --git a/evolve-die-repeat/scenes/windows/level_lost_window.tscn b/evolve-die-repeat/scenes/windows/level_lost_window.tscn
new file mode 100644
index 0000000..18f76b4
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/level_lost_window.tscn
@@ -0,0 +1,58 @@
+[gd_scene format=3 uid="uid://b31vngtb7lxa2"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_jt3uh"]
+[ext_resource type="Script" path="res://scenes/windows/level_lost_window.gd" id="2_6m2xx"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="3_o54tf"]
+
+[node name="LevelLostWindow" unique_id=493104306 instance=ExtResource("1_jt3uh")]
+custom_minimum_size = Vector2(432, 240)
+script = ExtResource("2_6m2xx")
+update_content = true
+text = "You lost..."
+close_button_text = "Restart"
+title_visible = false
+
+[node name="TitleMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="0" unique_id=1262022916]
+visible = false
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+text = "You lost..."
+
+[node name="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" parent_id_path=PackedInt32Array(1413292752) index="0" unique_id=1371114575]
+vertical = false
+
+[node name="ExitButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0" unique_id=1961310547]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Exit"
+
+[node name="MainMenuButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1" unique_id=1630609548]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Main Menu"
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="2" unique_id=314526102]
+size_flags_horizontal = 3
+text = "Restart"
+
+[node name="MainMenuConfirmation" parent="." index="1" unique_id=390342294 instance=ExtResource("3_o54tf")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(220, 0)
+layout_mode = 2
+text = "Exit to the main menu?"
+title = "Confirm Exit"
+
+[node name="ExitConfirmation" parent="." index="2" unique_id=2076311752 instance=ExtResource("3_o54tf")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Exit the game?"
+title = "Confirm Exit"
+
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ExitButton" to="." method="_on_exit_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/MainMenuButton" to="." method="_on_main_menu_button_pressed"]
+[connection signal="confirmed" from="MainMenuConfirmation" to="." method="_on_main_menu_confirmation_confirmed"]
+[connection signal="confirmed" from="ExitConfirmation" to="." method="_on_exit_confirmation_confirmed"]
diff --git a/evolve-die-repeat/scenes/windows/level_won_window.gd b/evolve-die-repeat/scenes/windows/level_won_window.gd
new file mode 100644
index 0000000..23ce670
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/level_won_window.gd
@@ -0,0 +1,31 @@
+@tool
+extends OverlaidWindow
+
+signal continue_pressed
+signal main_menu_pressed
+signal restart_pressed
+
+func _ready():
+ if OS.has_feature("web"):
+ %ExitButton.hide()
+
+func _on_exit_button_pressed():
+ %ExitConfirmation.show()
+
+func _on_main_menu_button_pressed():
+ %MainMenuConfirmation.show()
+
+func _on_close_button_pressed():
+ continue_pressed.emit()
+ close()
+
+func _on_main_menu_confirmation_confirmed():
+ main_menu_pressed.emit()
+ close()
+
+func _on_restart_button_pressed():
+ restart_pressed.emit()
+ close()
+
+func _on_exit_confirmation_confirmed():
+ get_tree().quit()
diff --git a/evolve-die-repeat/scenes/windows/level_won_window.gd.uid b/evolve-die-repeat/scenes/windows/level_won_window.gd.uid
new file mode 100644
index 0000000..ebbe2a3
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/level_won_window.gd.uid
@@ -0,0 +1 @@
+uid://djwfx2841d4n1
diff --git a/evolve-die-repeat/scenes/windows/level_won_window.tscn b/evolve-die-repeat/scenes/windows/level_won_window.tscn
new file mode 100644
index 0000000..62dba2a
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/level_won_window.tscn
@@ -0,0 +1,66 @@
+[gd_scene format=3 uid="uid://ch8b5ccmxnjo2"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_218i2"]
+[ext_resource type="Script" path="res://scenes/windows/level_won_window.gd" id="2_2bpb8"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="3_ss43f"]
+
+[node name="LevelWonWindow" unique_id=568930815 instance=ExtResource("1_218i2")]
+custom_minimum_size = Vector2(432, 240)
+script = ExtResource("2_2bpb8")
+update_content = true
+text = "Level complete!"
+close_button_text = "Continue"
+title_visible = false
+
+[node name="TitleMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="0" unique_id=1262022916]
+visible = false
+
+[node name="DescriptionLabel" parent="ContentContainer/BoxContainer/BodyMargin" parent_id_path=PackedInt32Array(590613964) index="0" unique_id=617407155]
+text = "Level complete!"
+
+[node name="MenuButtons" parent="ContentContainer/BoxContainer/MenuButtonsMargin" parent_id_path=PackedInt32Array(1413292752) index="0" unique_id=1371114575]
+vertical = false
+
+[node name="ExitButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="0" unique_id=1553412824]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Exit"
+
+[node name="MainMenuButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="1" unique_id=2132893693]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Main Menu"
+
+[node name="RestartButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="2" unique_id=1391591256]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Restart"
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" index="3" unique_id=314526102]
+size_flags_horizontal = 3
+text = "Continue"
+
+[node name="MainMenuConfirmation" parent="." index="1" unique_id=1746461395 instance=ExtResource("3_ss43f")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(220, 0)
+layout_mode = 2
+text = "Exit to the main menu?"
+title = "Confirm Exit"
+
+[node name="ExitConfirmation" parent="." index="2" unique_id=1965127230 instance=ExtResource("3_ss43f")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Exit the game?"
+title = "Confirm Exit"
+
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ExitButton" to="." method="_on_exit_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/MainMenuButton" to="." method="_on_main_menu_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/RestartButton" to="." method="_on_restart_button_pressed"]
+[connection signal="confirmed" from="MainMenuConfirmation" to="." method="_on_main_menu_confirmation_confirmed"]
+[connection signal="confirmed" from="ExitConfirmation" to="." method="_on_exit_confirmation_confirmed"]
diff --git a/evolve-die-repeat/scenes/windows/main_menu_credits_window.gd b/evolve-die-repeat/scenes/windows/main_menu_credits_window.gd
new file mode 100644
index 0000000..4673b4e
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/main_menu_credits_window.gd
@@ -0,0 +1,7 @@
+@tool
+extends "res://addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.gd"
+
+func _ready() -> void:
+ super._ready()
+ if instance and instance.has_signal(&"end_reached"):
+ instance.connect(&"end_reached", close)
diff --git a/evolve-die-repeat/scenes/windows/main_menu_credits_window.gd.uid b/evolve-die-repeat/scenes/windows/main_menu_credits_window.gd.uid
new file mode 100644
index 0000000..8fe1c6e
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/main_menu_credits_window.gd.uid
@@ -0,0 +1 @@
+uid://civblior38i0f
diff --git a/evolve-die-repeat/scenes/windows/main_menu_credits_window.tscn b/evolve-die-repeat/scenes/windows/main_menu_credits_window.tscn
new file mode 100644
index 0000000..1ea96ab
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/main_menu_credits_window.tscn
@@ -0,0 +1,25 @@
+[gd_scene format=3 uid="uid://dlqsxp2qiqdpb"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.tscn" id="1_rkudv"]
+[ext_resource type="Script" path="res://scenes/windows/main_menu_credits_window.gd" id="2_bndrm"]
+[ext_resource type="PackedScene" path="res://scenes/credits/scrollable_credits.tscn" id="3_8w6bb"]
+
+[node name="MainMenuCreditsOverlaidWindow" unique_id=1453761674 instance=ExtResource("1_rkudv")]
+anchors_preset = 15
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 0.0
+offset_top = 0.0
+offset_right = 0.0
+offset_bottom = 0.0
+script = ExtResource("2_bndrm")
+packed_scene = ExtResource("3_8w6bb")
+
+[node name="TitleMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="0" unique_id=1262022916]
+visible = false
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="0" unique_id=314526102]
+size_flags_horizontal = 0
+text = "Back"
diff --git a/evolve-die-repeat/scenes/windows/main_menu_options_window.tscn b/evolve-die-repeat/scenes/windows/main_menu_options_window.tscn
new file mode 100644
index 0000000..b0614ed
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/main_menu_options_window.tscn
@@ -0,0 +1,28 @@
+[gd_scene format=3 uid="uid://bm14qu13csdji"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.tscn" id="1_miy0a"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/master_options_menu_with_tabs.tscn" id="2_mljqd"]
+
+[node name="MainMenuOptionsOverlaidWindow" unique_id=1787637973 instance=ExtResource("1_miy0a")]
+anchors_preset = 15
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 0.0
+offset_top = 0.0
+offset_right = 0.0
+offset_bottom = 0.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+packed_scene = ExtResource("2_mljqd")
+update_content = true
+close_button_text = "Back"
+title_visible = false
+
+[node name="TitleMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="0" unique_id=1262022916]
+visible = false
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="0" unique_id=314526102]
+size_flags_horizontal = 0
+text = "Back"
diff --git a/evolve-die-repeat/scenes/windows/pause_menu.gd b/evolve-die-repeat/scenes/windows/pause_menu.gd
new file mode 100644
index 0000000..6ffe761
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu.gd
@@ -0,0 +1,117 @@
+@tool
+extends OverlaidWindow
+
+@export var options_menu_scene : PackedScene
+## Path to a main menu scene.
+## Will attempt to read from AppConfig if left empty.
+@export_file("*.tscn") var main_menu_scene_path : String
+@export_node_path(&"ConfirmationOverlaidWindow") var restart_confirmation_node_path : NodePath
+@export_node_path(&"ConfirmationOverlaidWindow") var main_menu_confirmation_node_path : NodePath
+@export_node_path(&"ConfirmationOverlaidWindow") var exit_confirmation_node_path : NodePath
+@export var menu_container_node_path : NodePath = ^".."
+
+@onready var restart_confirmation : ConfirmationOverlaidWindow = get_node(restart_confirmation_node_path)
+@onready var main_menu_confirmation : ConfirmationOverlaidWindow = get_node(main_menu_confirmation_node_path)
+@onready var exit_confirmation : ConfirmationOverlaidWindow = get_node(exit_confirmation_node_path)
+@onready var menu_container : Node = get_node(menu_container_node_path)
+@onready var options_button = %OptionsButton
+@onready var main_menu_button = %MainMenuButton
+@onready var exit_button = %ExitButton
+
+var open_window : Node
+var _ignore_first_cancel : bool = false
+
+func get_main_menu_scene_path() -> String:
+ if main_menu_scene_path.is_empty():
+ return AppConfig.main_menu_scene_path
+ return main_menu_scene_path
+
+func close_window() -> void:
+ if open_window != null:
+ if open_window.has_method("close"):
+ open_window.close()
+ else:
+ open_window.hide()
+ open_window = null
+
+func _disable_focus() -> void:
+ for child in %MenuButtons.get_children():
+ if child is Control:
+ child.focus_mode = FOCUS_NONE
+
+func _enable_focus() -> void:
+ for child in %MenuButtons.get_children():
+ if child is Control:
+ child.focus_mode = FOCUS_ALL
+
+func _load_scene(scene_path: String) -> void:
+ _scene_tree.paused = false
+ SceneLoader.load_scene(scene_path)
+
+func _show_window(window : Control) -> void:
+ _disable_focus.call_deferred()
+ window.show()
+ open_window = window
+ await window.hidden
+ open_window = null
+ _enable_focus.call_deferred()
+
+func _load_and_show_menu(scene : PackedScene) -> void:
+ var window_instance : Control = scene.instantiate()
+ window_instance.visible = false
+ menu_container.add_child.call_deferred(window_instance)
+ await _show_window(window_instance)
+ window_instance.queue_free()
+
+func _handle_cancel_input() -> void:
+ if _ignore_first_cancel:
+ _ignore_first_cancel = false
+ return
+ if open_window != null:
+ close_window()
+ else:
+ super._handle_cancel_input()
+
+func show() -> void:
+ super.show()
+ if Input.is_action_pressed("ui_cancel"):
+ _ignore_first_cancel = true
+
+func _refresh_exit_button() -> void:
+ exit_button.visible = !OS.has_feature("web")
+
+func _refresh_options_button() -> void:
+ options_button.visible = options_menu_scene != null
+
+func _refresh_main_menu_button() -> void:
+ main_menu_button.visible = !get_main_menu_scene_path().is_empty()
+
+func _ready() -> void:
+ _refresh_exit_button()
+ _refresh_options_button()
+ _refresh_main_menu_button()
+ restart_confirmation.confirmed.connect(_on_restart_confirmation_confirmed)
+ main_menu_confirmation.confirmed.connect(_on_main_menu_confirmation_confirmed)
+ exit_confirmation.confirmed.connect(_on_exit_confirmation_confirmed)
+
+func _on_restart_button_pressed() -> void:
+ _show_window(restart_confirmation)
+
+func _on_options_button_pressed() -> void:
+ _load_and_show_menu(options_menu_scene)
+
+func _on_main_menu_button_pressed() -> void:
+ _show_window(main_menu_confirmation)
+
+func _on_exit_button_pressed() -> void:
+ _show_window(exit_confirmation)
+
+func _on_restart_confirmation_confirmed() -> void:
+ SceneLoader.reload_current_scene()
+ close()
+
+func _on_main_menu_confirmation_confirmed():
+ _load_scene(get_main_menu_scene_path())
+
+func _on_exit_confirmation_confirmed():
+ get_tree().quit()
diff --git a/evolve-die-repeat/scenes/windows/pause_menu.gd.uid b/evolve-die-repeat/scenes/windows/pause_menu.gd.uid
new file mode 100644
index 0000000..c1d0f2e
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu.gd.uid
@@ -0,0 +1 @@
+uid://rgxs4ktm1qf1
diff --git a/evolve-die-repeat/scenes/windows/pause_menu.tscn b/evolve-die-repeat/scenes/windows/pause_menu.tscn
new file mode 100644
index 0000000..945f753
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu.tscn
@@ -0,0 +1,82 @@
+[gd_scene format=3 uid="uid://qyv16uiuxokm"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="1_yn032"]
+[ext_resource type="Script" path="res://scenes/windows/pause_menu.gd" id="2_00o4m"]
+[ext_resource type="PackedScene" path="res://scenes/windows/pause_menu_options_window.tscn" id="3_mdvg7"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="4_6lma4"]
+
+[node name="PauseMenu" unique_id=156357199 instance=ExtResource("1_yn032")]
+process_mode = 3
+custom_minimum_size = Vector2(256, 312)
+offset_left = -128.0
+offset_top = -155.0
+offset_right = 128.0
+offset_bottom = 157.0
+size_flags_horizontal = 1
+size_flags_vertical = 1
+script = ExtResource("2_00o4m")
+options_menu_scene = ExtResource("3_mdvg7")
+main_menu_scene_path = ""
+restart_confirmation_node_path = NodePath("RestartConfirmation")
+main_menu_confirmation_node_path = NodePath("MainMenuConfirmation")
+exit_confirmation_node_path = NodePath("ExitConfirmation")
+menu_container_node_path = NodePath("..")
+pauses_game = true
+
+[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(1788474031) index="0" unique_id=1049966061]
+text = "Paused"
+
+[node name="BodyMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="1" unique_id=590613964]
+visible = false
+
+[node name="MenuButtonsMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="2" unique_id=1413292752]
+size_flags_vertical = 3
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="0" unique_id=314526102]
+text = "Resume"
+
+[node name="RestartButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="1" unique_id=1371657172]
+layout_mode = 2
+text = "Restart"
+
+[node name="OptionsButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="2" unique_id=1126383675]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Options"
+
+[node name="MainMenuButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="3" unique_id=928043706]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Main Menu"
+
+[node name="ExitButton" type="Button" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="4" unique_id=1398257582]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Exit Game"
+
+[node name="RestartConfirmation" parent="." index="1" unique_id=1405814931 instance=ExtResource("4_6lma4")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Restart the game?"
+title = "Confirm Restart"
+
+[node name="MainMenuConfirmation" parent="." index="2" unique_id=1770307648 instance=ExtResource("4_6lma4")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(220, 0)
+layout_mode = 2
+text = "Exit to the main menu?"
+title = "Confirm Exit"
+
+[node name="ExitConfirmation" parent="." index="3" unique_id=1587469343 instance=ExtResource("4_6lma4")]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Exit the game?"
+title = "Confirm Exit"
+
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/RestartButton" to="." method="_on_restart_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/OptionsButton" to="." method="_on_options_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/MainMenuButton" to="." method="_on_main_menu_button_pressed"]
+[connection signal="pressed" from="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ExitButton" to="." method="_on_exit_button_pressed"]
diff --git a/evolve-die-repeat/scenes/windows/pause_menu_layer.gd b/evolve-die-repeat/scenes/windows/pause_menu_layer.gd
new file mode 100644
index 0000000..9ca9446
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu_layer.gd
@@ -0,0 +1,13 @@
+extends CanvasLayer
+
+@onready var pause_menu = %PauseMenu
+
+func _on_pause_menu_hidden():
+ hide()
+
+func _on_visibility_changed():
+ if visible:
+ pause_menu.show()
+
+func _ready():
+ visibility_changed.connect(_on_visibility_changed)
diff --git a/evolve-die-repeat/scenes/windows/pause_menu_layer.gd.uid b/evolve-die-repeat/scenes/windows/pause_menu_layer.gd.uid
new file mode 100644
index 0000000..5370131
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu_layer.gd.uid
@@ -0,0 +1 @@
+uid://df8ch4715k8oo
diff --git a/evolve-die-repeat/scenes/windows/pause_menu_layer.tscn b/evolve-die-repeat/scenes/windows/pause_menu_layer.tscn
new file mode 100644
index 0000000..25d6b0a
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu_layer.tscn
@@ -0,0 +1,98 @@
+[gd_scene format=3 uid="uid://drd6kloy08obm"]
+
+[ext_resource type="Script" path="res://scenes/windows/pause_menu_layer.gd" id="1_i1w7n"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window.tscn" id="2_1r40g"]
+[ext_resource type="Script" path="res://scenes/windows/pause_menu.gd" id="3_okuwl"]
+[ext_resource type="PackedScene" path="res://scenes/windows/pause_menu_options_window.tscn" id="4_mh1be"]
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/confirmation_overlaid_window.tscn" id="5_bgbk1"]
+
+[node name="PauseMenuLayer" type="CanvasLayer" unique_id=914002369]
+process_mode = 3
+script = ExtResource("1_i1w7n")
+
+[node name="PauseMenu" parent="." unique_id=6629802 instance=ExtResource("2_1r40g")]
+unique_name_in_owner = true
+process_mode = 3
+custom_minimum_size = Vector2(256, 312)
+script = ExtResource("3_okuwl")
+options_menu_scene = ExtResource("4_mh1be")
+main_menu_scene_path = ""
+restart_confirmation_node_path = NodePath("../RestartConfirmation")
+main_menu_confirmation_node_path = NodePath("../MainMenuConfirmation")
+exit_confirmation_node_path = NodePath("../ExitConfirmation")
+menu_container_node_path = NodePath("..")
+pauses_game = true
+update_content = true
+title = "Paused"
+
+[node name="TitleLabel" parent="PauseMenu/ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(6629802, 1788474031) index="0" unique_id=1049966061]
+text = "Paused"
+
+[node name="BodyMargin" parent="PauseMenu/ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(6629802, 394030069) index="1" unique_id=590613964]
+visible = false
+
+[node name="MenuButtonsMargin" parent="PauseMenu/ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(6629802, 394030069) index="2" unique_id=1413292752]
+size_flags_vertical = 3
+
+[node name="CloseButton" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="0" unique_id=314526102]
+text = "Resume"
+
+[node name="RestartButton" type="Button" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="1" unique_id=261040990]
+layout_mode = 2
+text = "Restart"
+
+[node name="SaveGameButton" type="Button" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="2" unique_id=1952904817]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Save Game"
+
+[node name="LoadGameButton" type="Button" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="3" unique_id=31108573]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+text = "Load Game"
+
+[node name="OptionsButton" type="Button" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="4" unique_id=487135325]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Options"
+
+[node name="MainMenuButton" type="Button" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="5" unique_id=920922356]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Main Menu"
+
+[node name="ExitButton" type="Button" parent="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(6629802, 1371114575) index="6" unique_id=2037900804]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Exit Game"
+
+[node name="RestartConfirmation" parent="." unique_id=1453441706 instance=ExtResource("5_bgbk1")]
+unique_name_in_owner = true
+visible = false
+text = "Restart the game?"
+title = "Confirm Restart"
+
+[node name="MainMenuConfirmation" parent="." unique_id=1848000740 instance=ExtResource("5_bgbk1")]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(220, 0)
+text = "Exit to the main menu?"
+title = "Confirm Exit"
+
+[node name="ExitConfirmation" parent="." unique_id=1161491301 instance=ExtResource("5_bgbk1")]
+unique_name_in_owner = true
+visible = false
+text = "Exit the game?"
+title = "Confirm Exit"
+
+[connection signal="hidden" from="PauseMenu" to="." method="_on_pause_menu_hidden"]
+[connection signal="pressed" from="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/RestartButton" to="PauseMenu" method="_on_restart_button_pressed"]
+[connection signal="pressed" from="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/SaveGameButton" to="PauseMenu" method="_on_save_game_button_pressed"]
+[connection signal="pressed" from="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/LoadGameButton" to="PauseMenu" method="_on_load_game_button_pressed"]
+[connection signal="pressed" from="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/OptionsButton" to="PauseMenu" method="_on_options_button_pressed"]
+[connection signal="pressed" from="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/MainMenuButton" to="PauseMenu" method="_on_main_menu_button_pressed"]
+[connection signal="pressed" from="PauseMenu/ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons/ExitButton" to="PauseMenu" method="_on_exit_button_pressed"]
+
+[editable path="PauseMenu"]
diff --git a/evolve-die-repeat/scenes/windows/pause_menu_options_window.tscn b/evolve-die-repeat/scenes/windows/pause_menu_options_window.tscn
new file mode 100644
index 0000000..f4116db
--- /dev/null
+++ b/evolve-die-repeat/scenes/windows/pause_menu_options_window.tscn
@@ -0,0 +1,28 @@
+[gd_scene format=3 uid="uid://b6beu33krojpq"]
+
+[ext_resource type="PackedScene" path="res://addons/maaacks_game_template/base/nodes/windows/overlaid_window_scene_container.tscn" id="1_a6sle"]
+[ext_resource type="PackedScene" path="res://scenes/menus/options_menu/master_options_menu_with_tabs.tscn" id="2_r1t3m"]
+
+[node name="PauseMenuOptionsOverlaidWindow" unique_id=701085877 instance=ExtResource("1_a6sle")]
+process_mode = 3
+anchors_preset = 15
+anchor_left = 0.0
+anchor_top = 0.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 0.0
+offset_top = 0.0
+offset_right = 0.0
+offset_bottom = 0.0
+packed_scene = ExtResource("2_r1t3m")
+title_visible = false
+
+[node name="TitleMargin" parent="ContentContainer/BoxContainer" parent_id_path=PackedInt32Array(394030069) index="0" unique_id=1262022916]
+visible = false
+
+[node name="TitleLabel" parent="ContentContainer/BoxContainer/TitleMargin/BoxContainer" parent_id_path=PackedInt32Array(1788474031) index="0" unique_id=1049966061]
+text = "Options"
+
+[node name="CloseButton" parent="ContentContainer/BoxContainer/MenuButtonsMargin/MenuButtons" parent_id_path=PackedInt32Array(1371114575) index="0" unique_id=314526102]
+size_flags_horizontal = 0
+text = "Back"
diff --git a/evolve-die-repeat/scripts/game_state.gd b/evolve-die-repeat/scripts/game_state.gd
new file mode 100644
index 0000000..87d5026
--- /dev/null
+++ b/evolve-die-repeat/scripts/game_state.gd
@@ -0,0 +1,79 @@
+class_name GameState
+extends Resource
+
+const STATE_NAME : String = "GameState"
+const FILE_PATH = "res://scripts/game_state.gd"
+
+@export var level_states : Dictionary = {}
+@export var current_level_path : String
+@export var checkpoint_level_path : String
+@export var total_games_played : int
+@export var play_time : int
+@export var total_time : int
+
+static func get_level_state(level_state_key : String) -> LevelState:
+ if not has_game_state():
+ return
+ var game_state := get_or_create_state()
+ if level_state_key.is_empty() : return
+ if level_state_key in game_state.level_states:
+ return game_state.level_states[level_state_key]
+ else:
+ var new_level_state := LevelState.new()
+ game_state.level_states[level_state_key] = new_level_state
+ GlobalState.save()
+ return new_level_state
+
+static func has_game_state() -> bool:
+ return GlobalState.has_state(STATE_NAME)
+
+static func get_or_create_state() -> GameState:
+ return GlobalState.get_or_create_state(STATE_NAME, FILE_PATH)
+
+static func get_current_level_path() -> String:
+ if not has_game_state():
+ return ""
+ var game_state := get_or_create_state()
+ return game_state.current_level_path
+
+static func get_checkpoint_level_path() -> String:
+ if not has_game_state():
+ return ""
+ var game_state := get_or_create_state()
+ return game_state.checkpoint_level_path
+
+static func get_levels_reached() -> int:
+ if not has_game_state():
+ return 0
+ var game_state := get_or_create_state()
+ return game_state.level_states.size()
+
+static func set_checkpoint_level_path(level_path : String) -> void:
+ var game_state := get_or_create_state()
+ game_state.checkpoint_level_path = level_path
+ get_level_state(level_path)
+ GlobalState.save()
+
+static func set_current_level_path(level_path : String) -> void:
+ var game_state := get_or_create_state()
+ game_state.current_level_path = level_path
+ GlobalState.save()
+
+static func start_game() -> void:
+ var game_state := get_or_create_state()
+ game_state.total_games_played += 1
+ GlobalState.save()
+
+static func continue_game() -> void:
+ var game_state := get_or_create_state()
+ game_state.current_level_path = game_state.checkpoint_level_path
+ GlobalState.save()
+
+static func reset() -> void:
+ var game_state := get_or_create_state()
+ game_state.level_states = {}
+ game_state.current_level_path = ""
+ game_state.checkpoint_level_path = ""
+ game_state.play_time = 0
+ game_state.total_time = 0
+ GlobalState.save()
diff --git a/evolve-die-repeat/scripts/game_state.gd.uid b/evolve-die-repeat/scripts/game_state.gd.uid
new file mode 100644
index 0000000..0561ff8
--- /dev/null
+++ b/evolve-die-repeat/scripts/game_state.gd.uid
@@ -0,0 +1 @@
+uid://c0e170hfdksei
diff --git a/evolve-die-repeat/scripts/level_and_state_manager.gd b/evolve-die-repeat/scripts/level_and_state_manager.gd
new file mode 100644
index 0000000..50458c4
--- /dev/null
+++ b/evolve-die-repeat/scripts/level_and_state_manager.gd
@@ -0,0 +1,15 @@
+extends LevelManager
+
+func set_current_level_path(value : String) -> void:
+ super.set_current_level_path(value)
+ GameState.set_current_level_path(value)
+
+func set_checkpoint_level_path(value : String) -> void:
+ super.set_checkpoint_level_path(value)
+ GameState.set_checkpoint_level_path(value)
+
+func get_checkpoint_level_path() -> String:
+ var state_level_path := GameState.get_checkpoint_level_path()
+ if not state_level_path.is_empty():
+ return state_level_path
+ return super.get_checkpoint_level_path()
diff --git a/evolve-die-repeat/scripts/level_and_state_manager.gd.uid b/evolve-die-repeat/scripts/level_and_state_manager.gd.uid
new file mode 100644
index 0000000..385bad2
--- /dev/null
+++ b/evolve-die-repeat/scripts/level_and_state_manager.gd.uid
@@ -0,0 +1 @@
+uid://c82ju21h300x5
diff --git a/evolve-die-repeat/scripts/level_state.gd b/evolve-die-repeat/scripts/level_state.gd
new file mode 100644
index 0000000..168aa2a
--- /dev/null
+++ b/evolve-die-repeat/scripts/level_state.gd
@@ -0,0 +1,5 @@
+class_name LevelState
+extends Resource
+
+@export var color : Color
+@export var tutorial_read : bool = false
diff --git a/evolve-die-repeat/scripts/level_state.gd.uid b/evolve-die-repeat/scripts/level_state.gd.uid
new file mode 100644
index 0000000..5ac2c96
--- /dev/null
+++ b/evolve-die-repeat/scripts/level_state.gd.uid
@@ -0,0 +1 @@
+uid://c3cymtvuy7tc7