├── .gitattributes ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── addons.jsonc ├── addons ├── .editorconfig └── enhanced_stat │ ├── icons │ ├── ecg_icon.png │ ├── icons8-square-24.png │ ├── icons8-stats-24 (1).png │ ├── icons8-stats-24.png │ ├── icons8-system-task-24.png │ └── pulse_icon.png │ ├── plugin.cfg │ ├── plugin.gd │ ├── scenes │ └── editor │ │ └── stats │ │ ├── bind │ │ ├── StatBindKeyButton.tscn │ │ └── StatBindPropertyButton.tscn │ │ ├── listeners │ │ └── StatListenerButton.tscn │ │ └── modifiers │ │ └── StatModifierButton.tscn │ └── scripts │ ├── core │ ├── stats │ │ ├── bind │ │ │ ├── abstract_stat_bind.gd │ │ │ └── stat_bind.gd │ │ ├── listeners │ │ │ └── stat_listener.gd │ │ ├── modifiers │ │ │ ├── aggregated │ │ │ │ ├── _aggregated_stat_modifier.gd │ │ │ │ └── pool │ │ │ │ │ ├── _pool_stat_modifier.gd │ │ │ │ │ ├── float_pool_stat_modifier.gd │ │ │ │ │ └── int_pool_stat_modifier.gd │ │ │ ├── computed │ │ │ │ ├── _computed_stat_modifier.gd │ │ │ │ ├── bool_stat_modifier.gd │ │ │ │ └── number │ │ │ │ │ ├── _number_stat_modifier.gd │ │ │ │ │ ├── float_stat_modifier.gd │ │ │ │ │ └── int_stat_modifier.gd │ │ │ └── stat_modifier.gd │ │ ├── stat │ │ │ ├── aggregated │ │ │ │ ├── _aggregated_stat.gd │ │ │ │ └── pool │ │ │ │ │ ├── _pool_stat.gd │ │ │ │ │ ├── float_pool_stat.gd │ │ │ │ │ ├── growable │ │ │ │ │ ├── _growable_pool_stat.gd │ │ │ │ │ ├── float_growable_pool_stat.gd │ │ │ │ │ └── int_growable_pool_stat.gd │ │ │ │ │ └── int_pool_stat.gd │ │ │ ├── computed │ │ │ │ ├── _computed_stat.gd │ │ │ │ ├── bool_stat.gd │ │ │ │ └── number │ │ │ │ │ ├── _number_stat.gd │ │ │ │ │ ├── float_stat.gd │ │ │ │ │ └── int_stat.gd │ │ │ └── stat.gd │ │ ├── stats.gd │ │ └── stats_manager.gd │ └── status_effects │ │ ├── components │ │ ├── _status_effect_component.gd │ │ ├── stat_modifier_status_effect_component.gd │ │ └── vfx_status_effect_component.gd │ │ ├── status_effect.gd │ │ ├── status_effect_abstract.gd │ │ ├── status_effect_applier.gd │ │ └── status_effect_manager.gd │ ├── editor │ └── stats │ │ ├── bind │ │ └── stat_bind_editor.gd │ │ ├── listeners │ │ ├── stat_listener_button.gd │ │ └── stat_listener_editor.gd │ │ └── modifiers │ │ ├── stat_modifier_button.gd │ │ └── stat_modifier_editor.gd │ └── utils │ └── stat_utils.gd ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── assets │ ├── logo.png │ ├── stat_binding_configuration.png │ ├── stat_binding_dropdown.png │ ├── stats_manager.png │ ├── status_effect_applier.png │ ├── stun.png │ └── stun_files.png ├── contribute.md ├── index.html └── manual │ ├── main.md │ ├── stats │ ├── aggregated.md │ ├── bind.md │ ├── computed.md │ ├── create.md │ ├── main.md │ └── modifiers.md │ └── status_effects │ ├── components.md │ └── main.md ├── examples ├── Main.tscn ├── MainCharacter.tscn ├── StunVfx.tscn ├── Trap.tscn ├── icons │ ├── icons8-heart-24.png │ ├── icons8-puppet-24.png │ ├── icons8-running-24.png │ └── icons8-speed-24.png ├── main_character.gd ├── stats │ ├── character_stats.gd │ ├── character_stats_manager.gd │ ├── controllable_stat.gd │ ├── health_stat.gd │ ├── main_character_stats.tres │ └── speed_stat.gd ├── stun.tres └── trap.gd └── project.godot /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | 4 | # Ignore some files when exporting to a ZIP. 5 | # Only include the addons folder when downloading from the Asset Library. 6 | /addons/enhance_common !export-ignore 7 | /addons/enhance_common/** !export-ignore 8 | 9 | # Also include script templates. 10 | /script_templates !export-ignore 11 | /script_templates/** !export-ignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !addons/.editorconfig 2 | # Godot 4+ specific ignores 3 | .godot/ 4 | # Godot-specific ignores 5 | .import/ 6 | **/*.import 7 | export.cfg 8 | export_presets.cfg 9 | 10 | # Mono-specific ignores 11 | .mono/ 12 | data_*/ 13 | .godot/ 14 | 15 | addons/enhanced_common/* 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for contributing to enhance. Feel free to fork this repository and raise a pull request. 2 | 3 | ## Contributing to Enhanced Stat :handshake: 4 | 5 | We warmly welcome contributions to the Enhanced Stat project! To maintain code quality and consistency, we follow the GitFlow branching model. Below is a quick guide on how to contribute using GitFlow. 6 | 7 | --- 8 | 9 | ### GitFlow Overview :book: 10 | 11 | The GitFlow workflow employs two parallel long-running branches to record the history of the project, `main` and `develop`: 12 | 13 | - **Main**: This branch reflects the latest stable release of the project. 14 | - **Develop**: This is the main branch where the source code of `HEAD` reflects a state with the latest delivered development changes for the next release. 15 | 16 | Feature branches, release branches, or hotfix branches will typically branch off from `develop`. 17 | 18 | --- 19 | 20 | ### Getting Started :rocket: 21 | 22 | 1. **Fork the Repository**: Start by forking the Enhanced Stat GitHub repository to your own GitHub account. 23 | 24 | 2. **Clone the Repository**: Clone your fork locally on your machine. 25 | 26 | ```bash 27 | git clone https://github.com/[YourUsername]/enhanced_stat.git 28 | ``` 29 | 30 | 3. **Set Upstream**: Add the original repository as an upstream remote. 31 | 32 | ```bash 33 | git remote add upstream https://github.com/original-repo/enhanced_stat.git 34 | ``` 35 | 36 | 4. **Create a Feature Branch**: Any new feature or bugfix should be developed in a separate branch. 37 | 38 | ```bash 39 | git checkout -b feature/your-feature-name develop 40 | ``` 41 | 42 | 5. **Commit Your Changes**: Make your changes and commit them. 43 | 44 | ```bash 45 | git add . 46 | git commit -m "Implemented feature X" 47 | ``` 48 | 49 | 6. **Push and Create a Pull Request**: Push your changes to your fork and create a pull request from there to the original repository. 50 | 51 | ```bash 52 | git push origin feature/your-feature-name 53 | ``` 54 | 55 | ### Code Review :mag: 56 | 57 | After you've made a pull request, maintainers and contributors will review your code. You may need to make some adjustments before your code is merged into the `develop` branch. 58 | 59 | --- 60 | 61 | By adhering to the GitFlow branching model, we ensure a clean and understandable history, make the development process more efficient, and facilitate contributions from multiple developers. Thank you for considering contributing to Enhanced Stat! :heart: -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zennyth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ✨ Enhanced Stat for Godot 4.1 2 | 3 | [](https://godotengine.org) 4 | [](https://opensource.org/licenses/MIT) 5 | [](https://zennyth.github.io/EnhancedStat/) 6 | 7 | --- 8 | 9 | ## 🔍 Table of Contents 10 | 11 | - [✨ Enhanced Stat for Godot 4.1](#-enhanced-stat-for-godot-41) 12 | - [🔍 Table of Contents](#-table-of-contents) 13 | - [📖 Overview](#-overview) 14 | - [📦 Installation](#-installation) 15 | - [🚀 Features](#-features) 16 | - [📈 Stats](#-stats) 17 | - [⚡ Status Effects](#-status-effects) 18 | - [💻 Editor Friendly](#-editor-friendly) 19 | - [🔗 Dependencies](#-dependencies) 20 | - [📁 Project Architecture](#-project-architecture) 21 | - [📗 Usage](#-usage) 22 | - [📝 Documentation](#-documentation) 23 | - [🍻 Contributing](#-contributing) 24 | - [📄 License](#-license) 25 | 26 | --- 27 | 28 | ## 📖 Overview 29 | 30 | **Enhanced Stat** is an addon suite for Godot 4.1, designed to provide an intuitive and powerful way to manage stats in games such as Health, Mana, Speed, and Attack Damage. This addon is part of the **Enhanced** suite aimed to make your Godot development faster, cleaner, and more enjoyable! 31 | 32 | --- 33 | 34 | ## 📦 Installation 35 | 36 | 1. Clone or download the repository. 37 | 2. Copy the `enhanced_stat` folder inside your project's `addons` directory. 38 | 3. Enable the addon by going to `Project -> Project Settings -> Plugins` and activating the "Enhanced Stat" plugin. 39 | 40 | --- 41 | 42 | ## 🚀 Features 43 | 44 | ### 📈 Stats 45 | 46 | - **Reactive Programming**: Stats are reactive, and changes can be listened to in real-time. 47 | - **Computed Stats**: Type safe basic stats. 48 | - **Aggregated Stats**: Create new stats based on existing stats, automatically updating when dependencies change. 49 | - **Bind Mechanism**: Allows easy binding of stats to in game nodes. 50 | - **Modifiers**: Flexible modification of stats through addition, multiplication, or other custom methods. 51 | - **Listeners**: Events to notify game logic of stats changes. 52 | 53 | ### ⚡ Status Effects 54 | 55 | - **Component-based**: Reusable components that can be combined to create complex status effects. 56 | - **Modifiers**: Attachable to stats to create temporary or permanent changes. 57 | - **Vfx**: Add vfxs or nodes while the status effect is active. 58 | 59 | ### 💻 Editor Friendly 60 | 61 | - **Editor Inspector**: Use the editor to configure the stats and status effects for your game. 62 | 63 | --- 64 | 65 | ## 🔗 Dependencies 66 | 67 | - **Enhanced Common**: This addon depends on the [enhanced_common](https://github.com/Zennyth/EnhancedCommon) addon for various utilities and common functionalities. Make sure to also have this addon installed and enabled. 68 | 69 | --- 70 | 71 | ## 📁 Project Architecture 72 | 73 | Here is an overview of the project's file hierarchy: 74 | 75 | ``` 76 | - enhanced_stat 77 | +---addons 78 | +---enhanced_common 79 | +---enhanced_stat 80 | +---icons 81 | +---scenes 82 | +---editor 83 | +---stats 84 | +---bind 85 | +---listeners 86 | +---modifier 87 | +---scripts 88 | +---editor 89 | +---stats 90 | +---bind 91 | +---listeners 92 | +---modifier 93 | +---core 94 | +---stats 95 | +---bind 96 | +---listeners 97 | +---modifier 98 | +---stat 99 | +---status_effects 100 | +---components 101 | ``` 102 | 103 | --- 104 | 105 | ## 📗 Usage 106 | 107 | Check the [documentation](https://zennyth.github.io/EnhancedStat/) for tutorials, API references, and examples to get started. Usage is straightforward and can easily be integrated into your existing Godot project. 108 | 109 | --- 110 | 111 | ## 📝 Documentation 112 | 113 | Further documentation is available in the `docs` directory. 114 | You can also check the inline comments for a more detailed understanding of specific methods and functionalities. 115 | 116 | --- 117 | 118 | ## 🍻 Contributing 119 | 120 | Contributions are very welcome! Feel free to open issues or submit pull requests. 121 | 122 | --- 123 | 124 | ## 📄 License 125 | 126 | This project is licensed under the terms of the MIT license. See [LICENSE](LICENSE) for details. 127 | This project uses icons from [icons8](https://icons8.com/license). If you fancy those lovely icons, please check out their [website](https://icons8.com/icon/set/logos/material). 128 | 129 | --- 130 | 131 | Made with ❤️ for the Godot community. Happy coding! :smile: 132 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Godot Version | Branch | Supported | 6 | | ------- | ----|-----|------------- | 7 | | 1.x | 4.x | `godot-4.x` | :white_check_mark: | 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Please [raise an issue](https://github.com/zennyth/enhanced_stat/issues) in case you find a security issue. -------------------------------------------------------------------------------- /addons.jsonc: -------------------------------------------------------------------------------- 1 | // Godot addons configuration file for use with the GodotEnv tool. 2 | // See https://github.com/chickensoft-games/GodotEnv for more info. 3 | // -------------------------------------------------------------------- // 4 | // Note: this is a JSONC file, so you can use comments! 5 | // If using Rider, see https://youtrack.jetbrains.com/issue/RIDER-41716 6 | // for any issues with JSONC. 7 | // -------------------------------------------------------------------- // 8 | { 9 | "$schema": "https://chickensoft.games/schemas/addons.schema.json", 10 | "cache": "../.addons", 11 | "addons": { 12 | "enhanced_common": { 13 | "url": "https://github.com/Zennyth/EnhancedCommon", 14 | "checkout": "develop", 15 | "subfolder": "addons/enhanced_common" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /addons/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configs in nested directories override those in parent directories 2 | # for the directory in which they are placed. 3 | # 4 | # This editor config prevents the code editor from analyzing C# files which 5 | # belong to addons. 6 | # 7 | # Ignoring C# addon scripts is generally preferable, since C# can be coded 8 | # in a variety of ways that may or may not trigger warnings based on your 9 | # own editorconfig or IDE settings. 10 | 11 | [*.cs] 12 | generated_code = true -------------------------------------------------------------------------------- /addons/enhanced_stat/icons/ecg_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/addons/enhanced_stat/icons/ecg_icon.png -------------------------------------------------------------------------------- /addons/enhanced_stat/icons/icons8-square-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/addons/enhanced_stat/icons/icons8-square-24.png -------------------------------------------------------------------------------- /addons/enhanced_stat/icons/icons8-stats-24 (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/addons/enhanced_stat/icons/icons8-stats-24 (1).png -------------------------------------------------------------------------------- /addons/enhanced_stat/icons/icons8-stats-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/addons/enhanced_stat/icons/icons8-stats-24.png -------------------------------------------------------------------------------- /addons/enhanced_stat/icons/icons8-system-task-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/addons/enhanced_stat/icons/icons8-system-task-24.png -------------------------------------------------------------------------------- /addons/enhanced_stat/icons/pulse_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/addons/enhanced_stat/icons/pulse_icon.png -------------------------------------------------------------------------------- /addons/enhanced_stat/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="EnhancedStat" 4 | description="Enhanced Stat is an addon suite for Godot 4.1, designed to provide an intuitive and powerful way to manage stats in games such as Health, Mana, Speed, and Attack Damage. This addon is part of the Enhanced suite aimed to make your Godot development faster, cleaner, and more enjoyable!" 5 | author="Zennyth" 6 | version="0.0.2" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/enhanced_stat/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | 5 | const StatModifierEditorInspector = preload("res://addons/enhanced_stat/scripts/editor/stats/modifiers/stat_modifier_editor.gd") 6 | var stat_modifier_editor_inspector: StatModifierEditorInspector 7 | 8 | const StatBindEditorInspector = preload("res://addons/enhanced_stat/scripts/editor/stats/bind/stat_bind_editor.gd") 9 | var stat_bind_editor_inspector: StatBindEditorInspector 10 | 11 | const StatListenerEditorInspector = preload("res://addons/enhanced_stat/scripts/editor/stats/listeners/stat_listener_editor.gd") 12 | var stat_listener_editor_inspector: StatListenerEditorInspector 13 | 14 | func _enter_tree(): 15 | stat_modifier_editor_inspector = StatModifierEditorInspector.new() 16 | add_inspector_plugin(stat_modifier_editor_inspector) 17 | 18 | stat_bind_editor_inspector = StatBindEditorInspector.new() 19 | add_inspector_plugin(stat_bind_editor_inspector) 20 | 21 | stat_listener_editor_inspector = StatListenerEditorInspector.new() 22 | add_inspector_plugin(stat_listener_editor_inspector) 23 | 24 | func _exit_tree(): 25 | remove_inspector_plugin(stat_modifier_editor_inspector) 26 | remove_inspector_plugin(stat_bind_editor_inspector) 27 | remove_inspector_plugin(stat_listener_editor_inspector) -------------------------------------------------------------------------------- /addons/enhanced_stat/scenes/editor/stats/bind/StatBindKeyButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://1pf1oqyag73b"] 2 | 3 | [ext_resource type="Script" path="res://addons/enhanced_stat/scripts/editor/stats/bind/stat_bind_key_button.gd" id="1_vib5v"] 4 | 5 | [node name="OptionButton" type="OptionButton"] 6 | offset_left = 638.0 7 | offset_right = 1272.0 8 | offset_bottom = 720.0 9 | size_flags_horizontal = 3 10 | theme_override_colors/font_color = Color(1, 1, 1, 1) 11 | text_overrun_behavior = 3 12 | fit_to_longest_item = false 13 | script = ExtResource("1_vib5v") 14 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scenes/editor/stats/bind/StatBindPropertyButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cnxrnpb1eur4m"] 2 | 3 | [ext_resource type="Script" path="res://addons/enhanced_stat/scripts/editor/stats/bind/stat_bind_property_button.gd" id="1_jsuxr"] 4 | 5 | [node name="OptionButton" type="OptionButton"] 6 | offset_left = 638.0 7 | offset_right = 1272.0 8 | offset_bottom = 720.0 9 | size_flags_horizontal = 3 10 | theme_override_colors/font_color = Color(1, 1, 1, 1) 11 | text_overrun_behavior = 3 12 | fit_to_longest_item = false 13 | script = ExtResource("1_jsuxr") 14 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scenes/editor/stats/listeners/StatListenerButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://b0mu6ixl6ymjn"] 2 | 3 | [ext_resource type="Script" path="res://addons/enhanced_stat/scripts/editor/stats/listeners/stat_listener_button.gd" id="1_68eqh"] 4 | 5 | [node name="OptionButton" type="OptionButton"] 6 | offset_left = 638.0 7 | offset_right = 1272.0 8 | offset_bottom = 720.0 9 | size_flags_horizontal = 3 10 | theme_override_colors/font_color = Color(1, 1, 1, 1) 11 | text_overrun_behavior = 3 12 | fit_to_longest_item = false 13 | script = ExtResource("1_68eqh") 14 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scenes/editor/stats/modifiers/StatModifierButton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://b0mu6ixl6ymjn"] 2 | 3 | [ext_resource type="Script" path="res://addons/enhanced_stat/scripts/editor/stats/modifiers/stat_modifier_button.gd" id="1_68eqh"] 4 | 5 | [node name="OptionButton" type="OptionButton"] 6 | offset_left = 638.0 7 | offset_right = 1272.0 8 | offset_bottom = 720.0 9 | size_flags_horizontal = 3 10 | theme_override_colors/font_color = Color(1, 1, 1, 1) 11 | text_overrun_behavior = 3 12 | fit_to_longest_item = false 13 | script = ExtResource("1_68eqh") 14 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/bind/abstract_stat_bind.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | @icon("res://addons/enhanced_stat/icons/icons8-stats-24.png") 3 | extends Node 4 | class_name AbstractStatBind 5 | 6 | enum Mode { 7 | BIND_STAT, 8 | BIND_VALUE, 9 | } 10 | 11 | @export var mode: Mode = Mode.BIND_STAT 12 | @export var target: Node: 13 | set(value): 14 | target = value 15 | notify_property_list_changed() 16 | @export var stat_key: GDScript 17 | @export var property_to_bind: String 18 | 19 | 20 | var _on_stat_value_changed 21 | var _stats_manager: StatsManager: 22 | get = get_stats_manager 23 | 24 | func get_stats_manager() -> StatsManager: 25 | return null 26 | 27 | 28 | func _ready() -> void: 29 | if Engine.is_editor_hint(): 30 | return 31 | 32 | bind() 33 | 34 | func bind() -> void: 35 | if mode == Mode.BIND_STAT: 36 | StatUtils.bind_stat(target, property_to_bind, stat_key, _stats_manager) 37 | elif mode == Mode.BIND_VALUE: 38 | _on_stat_value_changed = StatUtils.bind_stat_value(target, property_to_bind, stat_key, _stats_manager) 39 | 40 | func unbind() -> void: 41 | if mode == Mode.BIND_STAT: 42 | StatUtils.unbind_stat(target, property_to_bind) 43 | elif mode == Mode.BIND_VALUE: 44 | StatUtils.unbind_stat_value(target, property_to_bind, _on_stat_value_changed) 45 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/bind/stat_bind.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends AbstractStatBind 3 | class_name StatBind 4 | 5 | @export var stats_manager: StatsManager: 6 | set(value): 7 | stats_manager = value 8 | notify_property_list_changed() 9 | 10 | func get_stats_manager() -> StatsManager: 11 | return stats_manager -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/listeners/stat_listener.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_common/icons/icons8-trigger-24.png") 2 | extends Node 3 | class_name StatListener 4 | 5 | signal triggered 6 | 7 | @export var stats_manager: StatsManager 8 | @export var stat_key: GDScript 9 | @export var signal_name: String 10 | 11 | func _ready() -> void: 12 | if stats_manager == null or stat_key == null or signal_name == null: 13 | return 14 | 15 | var stat: Stat = stats_manager.get_stat(stat_key) 16 | 17 | if stat == null or !stat.has_signal(signal_name): 18 | return 19 | 20 | stat.get(signal_name).connect(_on_triggered) 21 | 22 | func _on_triggered(arg1 = null, arg2 = null, arg3 = null, arg4 = null) -> void: 23 | triggered.emit() 24 | 25 | 26 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/aggregated/_aggregated_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends StatModifier 2 | class_name AggregatedStatModifier 3 | 4 | 5 | func set_apply_count(value: int) -> void: 6 | super(value) 7 | 8 | for stat_modifier in stat_modifiers: 9 | stat_modifier.apply_count = apply_count 10 | 11 | 12 | 13 | var stat_modifiers: Array[StatModifier]: 14 | get = get_stat_modifiers 15 | 16 | func get_stat_modifiers() -> Array[StatModifier]: 17 | var res: Array[StatModifier] = [] 18 | 19 | for property in get_property_list(): 20 | if property.usage == 4102 and get(property.name) is StatModifier: 21 | res.append(get(property.name) as StatModifier) 22 | 23 | return res -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/aggregated/pool/_pool_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends AggregatedStatModifier 2 | class_name PoolStatModifier 3 | 4 | @export var is_infinite: BoolStatModifier -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/aggregated/pool/float_pool_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends PoolStatModifier 2 | class_name FloatPoolStatModifier 3 | 4 | @export var max: FloatStatModifier 5 | @export var min: FloatStatModifier -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/aggregated/pool/int_pool_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends PoolStatModifier 2 | class_name IntPoolStatModifier 3 | 4 | @export var max: IntStatModifier 5 | @export var min: IntStatModifier -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/computed/_computed_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends StatModifier 2 | class_name ComputedStatModifier 3 | 4 | func apply(_value, _stat): 5 | return null -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/computed/bool_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends ComputedStatModifier 2 | class_name BoolStatModifier 3 | 4 | @export var new_value: bool = false 5 | 6 | func apply(_value: bool, _stat: BoolStat): 7 | return new_value if apply_count > 0 else _value -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/computed/number/_number_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends ComputedStatModifier 2 | class_name NumberStatModifier 3 | 4 | enum Operation { 5 | ADD, 6 | SUBSTRACT, 7 | MULTIPLY, 8 | DIVIDE 9 | } 10 | 11 | @export var operation: Operation = Operation.ADD 12 | 13 | var _operation_value: 14 | get = get_operation_value 15 | 16 | func get_operation_value(): 17 | return 0 18 | 19 | func apply(value, _stat): 20 | match operation: 21 | Operation.ADD: 22 | return value + (_operation_value * apply_count) 23 | Operation.SUBSTRACT: 24 | return value - (_operation_value * apply_count) 25 | Operation.MULTIPLY: 26 | return value * _operation_value * apply_count 27 | Operation.DIVIDE: 28 | if _operation_value == 0: 29 | push_error("NumberStatModifier cannot divide by 0") 30 | return value 31 | 32 | return value / _operation_value * apply_count 33 | 34 | return value -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/computed/number/float_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends NumberStatModifier 2 | class_name FloatStatModifier 3 | 4 | @export var operation_value: float = 0 5 | 6 | func get_operation_value() -> float: 7 | return operation_value 8 | 9 | func apply(value: float, stat: FloatStat): 10 | return super(value, stat) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/computed/number/int_stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends NumberStatModifier 2 | class_name IntStatModifier 3 | 4 | @export var operation_value: int = 0 5 | 6 | func get_operation_value() -> int: 7 | return operation_value 8 | 9 | func apply(value: int, stat: IntStat): 10 | return super(value, stat) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/modifiers/stat_modifier.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | class_name StatModifier 3 | 4 | signal apply_count_changed() 5 | 6 | 7 | @export var stat_key: GDScript 8 | @export var apply_count: int = 1: 9 | set = set_apply_count 10 | 11 | func set_apply_count(value: int) -> void: 12 | apply_count = value 13 | apply_count_changed.emit() 14 | 15 | 16 | func compatible_with(stat: Stat) -> bool: 17 | return stat_key == stat.key -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/_aggregated_stat.gd: -------------------------------------------------------------------------------- 1 | extends Stat 2 | class_name AggregatedStat 3 | 4 | 5 | ### 6 | # CORE 7 | ### 8 | var _value: 9 | set = set_value 10 | 11 | func get_value(): 12 | return _value 13 | 14 | func set_value(new_value): 15 | var is_different: bool = new_value != _value 16 | _value = new_value 17 | 18 | if is_different: 19 | value_changed.emit() 20 | 21 | 22 | ### 23 | # STAT MODIFIERS 24 | ### 25 | var internal_stats: Array[Stat]: 26 | get = get_internal_stats 27 | 28 | func get_internal_stats() -> Array[Stat]: 29 | var res: Array[Stat] = [] 30 | 31 | for property in get_property_list(): 32 | if property.usage == 4102 and get(property.name) is Stat: 33 | res.append(get(property.name) as Stat) 34 | 35 | return res 36 | 37 | 38 | func add_stat_modifier(stat_modifier: StatModifier) -> void: 39 | for property in stat_modifier.get_property_list(): 40 | if !property.name in self or !get(property.name) is Stat or stat_modifier.get(property.name) == null: 41 | continue 42 | 43 | get(property.name).add_stat_modifier(stat_modifier.get(property.name)) 44 | 45 | func remove_stat_modifier(stat_modifier: StatModifier) -> void: 46 | for property in stat_modifier.get_property_list(): 47 | if !property.name in self or !get(property.name) is Stat: 48 | continue 49 | 50 | get(property.name).remove_stat_modifier(stat_modifier.get(property.name)) 51 | 52 | func clear_stat_modifiers() -> void: 53 | for internal_stat in internal_stats: 54 | internal_stat.clear_stat_modifiers() 55 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/pool/_pool_stat.gd: -------------------------------------------------------------------------------- 1 | extends AggregatedStat 2 | class_name PoolStat 3 | 4 | signal depleted() 5 | signal replenished() 6 | signal increased(value, amount) 7 | signal decreased(value, amount) 8 | 9 | 10 | var _max: NumberStat 11 | var _min: NumberStat 12 | @export var is_infinite: BoolStat 13 | 14 | 15 | func _on_max_increased(_max_value, increase_amount) -> void: 16 | _value += increase_amount 17 | 18 | func _on_max_decreased(_max_value, _decrease_amount) -> void: 19 | _value = _value 20 | 21 | 22 | func initialize() -> void: 23 | if base_value == -1: 24 | base_value = _max.value 25 | 26 | _value = base_value 27 | SignalUtils.connect_if_not_connected(_max.increased, _on_max_increased) 28 | SignalUtils.connect_if_not_connected(_max.decreased, _on_max_decreased) 29 | # SignalUtils.connect_if_not_connected(_min.value_changed, update_value) 30 | 31 | func set_default_value(_default_value) -> void: 32 | base_value = _default_value 33 | 34 | if _max == null or _min == null: 35 | return 36 | 37 | initialize() 38 | 39 | func set_value(new_value) -> void: 40 | if is_infinite != null and is_infinite.value: 41 | return 42 | 43 | var previous = _value 44 | new_value = min(new_value, _max.value) 45 | new_value = max(new_value, _min.value) 46 | 47 | super(new_value) 48 | 49 | if previous == null: 50 | return 51 | 52 | var amount = _value - previous 53 | if amount > 0: 54 | increased.emit(new_value, amount) 55 | elif amount < 0: 56 | decreased.emit(new_value, amount) 57 | else: 58 | return 59 | 60 | if is_empty(): 61 | depleted.emit() 62 | elif is_full(): 63 | replenished.emit() 64 | 65 | 66 | func decrease(to_decrease) -> void: 67 | _value -= to_decrease 68 | 69 | func increase(to_increase) -> void: 70 | _value += to_increase 71 | 72 | func replenish() -> void: 73 | _value = _max.value 74 | 75 | func deplete() -> void: 76 | _value = _min.value 77 | 78 | 79 | func is_full() -> bool: 80 | return _max.value == _value 81 | 82 | func is_empty() -> bool: 83 | return _min.value == _value 84 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/pool/float_pool_stat.gd: -------------------------------------------------------------------------------- 1 | extends PoolStat 2 | class_name FloatPoolStat 3 | 4 | 5 | @export var max: FloatStat = FloatStat.new(): 6 | set(_max_value): 7 | max = _max_value 8 | _max = max 9 | set_default_value(default_value) 10 | 11 | @export var min: FloatStat = FloatStat.new(): 12 | set(_min_value): 13 | min = _min_value 14 | _min = min 15 | set_default_value(default_value) 16 | 17 | @export var default_value: float = 0: 18 | set(_default_value): 19 | default_value = _default_value 20 | set_default_value(default_value) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/pool/growable/_growable_pool_stat.gd: -------------------------------------------------------------------------------- 1 | extends PoolStat 2 | class_name GrowablePoolStat 3 | 4 | signal leveled_up 5 | 6 | @export var level: int = 1 7 | 8 | func grow() -> void: 9 | pass 10 | 11 | func set_value(new_value) -> void: 12 | if _value == null or new_value < _max.value: 13 | return super(new_value) 14 | 15 | var difference = new_value - _max.value 16 | grow() 17 | level += 1 18 | value_changed.emit() 19 | leveled_up.emit() 20 | set_value(difference) 21 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/pool/growable/float_growable_pool_stat.gd: -------------------------------------------------------------------------------- 1 | extends GrowablePoolStat 2 | class_name FloatGrowablePoolStat 3 | 4 | 5 | @export var max: FloatStat = FloatStat.new(): 6 | set(_max_value): 7 | max = _max_value 8 | _max = max 9 | 10 | @export var min: FloatStat = FloatStat.new(): 11 | set(_min_value): 12 | min = _min_value 13 | _min = min 14 | 15 | @export var default_value: float = 0: 16 | set(_default_value): 17 | default_value = _default_value 18 | set_default_value(default_value) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/pool/growable/int_growable_pool_stat.gd: -------------------------------------------------------------------------------- 1 | extends GrowablePoolStat 2 | class_name IntGrowablePoolStat 3 | 4 | 5 | @export var max: IntStat = IntStat.new(): 6 | set(_max_value): 7 | max = _max_value 8 | _max = max 9 | 10 | @export var min: IntStat = IntStat.new(): 11 | set(_min_value): 12 | min = _min_value 13 | _min = min 14 | 15 | @export var default_value: int = 0: 16 | set(_default_value): 17 | default_value = _default_value 18 | set_default_value(default_value) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/aggregated/pool/int_pool_stat.gd: -------------------------------------------------------------------------------- 1 | extends PoolStat 2 | class_name IntPoolStat 3 | 4 | 5 | @export var max: IntStat = IntStat.new(): 6 | set(_max_value): 7 | max = _max_value 8 | _max = max 9 | set_default_value(default_value) 10 | 11 | @export var min: IntStat = IntStat.new(): 12 | set(_min_value): 13 | min = _min_value 14 | _min = min 15 | set_default_value(default_value) 16 | 17 | @export var default_value: int = -1: 18 | set(_default_value): 19 | default_value = _default_value 20 | set_default_value(default_value) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/computed/_computed_stat.gd: -------------------------------------------------------------------------------- 1 | extends Stat 2 | class_name ComputedStat 3 | 4 | 5 | ### 6 | # CORE 7 | ### 8 | var _value 9 | 10 | func get_value(): 11 | return _value 12 | 13 | func compute() -> void: 14 | var computed = base_value 15 | 16 | for stat_modifier in stat_modifiers: 17 | computed = stat_modifier.apply(computed, self) 18 | 19 | _value = computed 20 | value_changed.emit() 21 | 22 | 23 | ### 24 | # STAT MODIFIERS 25 | ### 26 | var stat_modifiers: Array[StatModifier] = [] 27 | 28 | func add_stat_modifier(stat_modifier: StatModifier) -> void: 29 | stat_modifiers.append(stat_modifier) 30 | stat_modifier.apply_count_changed.connect(compute) 31 | compute() 32 | 33 | func remove_stat_modifier(stat_modifier: StatModifier) -> void: 34 | stat_modifiers.erase(stat_modifier) 35 | stat_modifier.apply_count_changed.disconnect(compute) 36 | compute() 37 | 38 | func clear_stat_modifiers() -> void: 39 | for stat_modifier in stat_modifiers: 40 | stat_modifier.apply_count_changed.disconnect(compute) 41 | 42 | stat_modifiers = [] 43 | compute() 44 | 45 | 46 | 47 | ### 48 | # CONSTRUCTOR 49 | ### 50 | func _init(_base_value = base_value) -> void: 51 | base_value = _base_value 52 | compute() 53 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/computed/bool_stat.gd: -------------------------------------------------------------------------------- 1 | extends ComputedStat 2 | class_name BoolStat 3 | 4 | @export var default_value: bool = false: 5 | set(_default_value): 6 | default_value = _default_value 7 | base_value = default_value 8 | _value = default_value 9 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/computed/number/_number_stat.gd: -------------------------------------------------------------------------------- 1 | extends ComputedStat 2 | class_name NumberStat 3 | 4 | signal increased(value, amount) 5 | signal decreased(value, amount) 6 | 7 | func compute() -> void: 8 | var previous = _value 9 | 10 | super() 11 | 12 | if previous == null: 13 | return 14 | 15 | var amount = _value - previous 16 | 17 | if amount > 0: 18 | increased.emit(value, amount) 19 | elif amount < 0: 20 | decreased.emit(value, amount) 21 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/computed/number/float_stat.gd: -------------------------------------------------------------------------------- 1 | extends NumberStat 2 | class_name FloatStat 3 | 4 | @export var default_value: float = .0: 5 | set(_default_value): 6 | default_value = _default_value 7 | base_value = default_value 8 | _value = default_value 9 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/computed/number/int_stat.gd: -------------------------------------------------------------------------------- 1 | extends NumberStat 2 | class_name IntStat 3 | 4 | @export var default_value: int = 0: 5 | set(_default_value): 6 | default_value = _default_value 7 | base_value = default_value 8 | _value = default_value 9 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stat/stat.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_common/icons/icons8-stats-24.png") 2 | extends Resource 3 | class_name Stat 4 | ## The Stat class is used for representing individual stats, like Health, Mana, Speed, etc. 5 | ## 6 | ## As it inherits from Godot's Resource, it is easily serializable and can be shared among multiple instances or saved for later. 7 | 8 | ### 9 | # CORE 10 | ### 11 | 12 | ## Key that defines the uniqueness of a stat 13 | var key: Variant: 14 | get = get_key 15 | 16 | ## Get the key 17 | func get_key() -> Variant: 18 | return get_script() 19 | 20 | ## 21 | signal value_changed 22 | 23 | var value: 24 | get = get_value 25 | 26 | func get_value(): 27 | return base_value 28 | 29 | 30 | var base_value 31 | 32 | 33 | ### 34 | # STAT MODIFIERS 35 | ### 36 | func add_stat_modifier(stat_modifier) -> void: 37 | pass 38 | 39 | func remove_stat_modifier(stat_modifier) -> void: 40 | pass 41 | 42 | func clear_stat_modifiers() -> void: 43 | pass -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stats.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_common/icons/icons8-stats-24 (1).png") 2 | extends Resource 3 | class_name Stats 4 | ## The Stats class is essentially a container for Stat objects. 5 | ## 6 | ## It is useful when you have an entity that has multiple stats, 7 | ## like a character in an RPG who has health, mana, strength, etc. 8 | 9 | 10 | ### 11 | # STATS 12 | ### 13 | 14 | ## Dictionary that holds a look up for (key, stat) 15 | var map: Dictionary: 16 | get = get_map_from_list 17 | 18 | ## Generate the look up table from a list of stat 19 | func get_map_from_list() -> Dictionary: 20 | var new_map = {} 21 | 22 | for property in get_property_list(): 23 | # if not exported 24 | if property.get("usage") != 4102: 25 | continue 26 | 27 | var stat = get(property.get("name")) 28 | 29 | if not stat is Stat: 30 | continue 31 | 32 | if stat.key in new_map: 33 | push_warning("Found duplicate key: ", stat.key, " in stats: ", self) 34 | continue 35 | 36 | new_map[stat.key] = stat 37 | 38 | return new_map 39 | 40 | ## Get the look up table as an array 41 | func get_stats() -> Array: 42 | return map.values() 43 | 44 | ## Get a specific stat by it's key 45 | func get_stat(key) -> Stat: 46 | return map[key] if key in map else null 47 | 48 | ## Check whether the key is in the look up table 49 | func has_stat(key) -> bool: 50 | return map.has(key) 51 | 52 | 53 | 54 | ### 55 | # STAT MODIFIERS 56 | ### 57 | 58 | ## Add a stat modifier to a stat 59 | func add_stat_modifier(stat_modifier: StatModifier) -> void: 60 | var stat: Stat = get_stat(stat_modifier.stat_key) 61 | 62 | if stat == null: 63 | return 64 | 65 | stat.add_stat_modifier(stat_modifier) 66 | 67 | ## Remove a stat modifier from a stat 68 | func remove_stat_modifier(stat_modifier: StatModifier) -> void: 69 | var stat: Stat = get_stat(stat_modifier.stat_key) 70 | 71 | if stat == null: 72 | return 73 | 74 | stat.remove_stat_modifier(stat_modifier) 75 | 76 | ## Clear all stat modifiers from all stats 77 | func clear_stat_modifiers() -> void: 78 | for stat in get_stats(): 79 | stat.clear_stat_modifiers() -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/stats/stats_manager.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | @icon("res://addons/enhanced_stat/icons/icons8-stats-24 (1).png") 3 | extends Node 4 | class_name StatsManager 5 | ## The StatsManager class provides an interface to manage Stats and is typically attached to the entity that owns those stats. 6 | ## This makes it easier to access and manipulate stats during gameplay. 7 | 8 | ## Stats holder 9 | var stats: Stats: 10 | set = set_stats 11 | 12 | var _stats: Stats 13 | 14 | func set_stats(value) -> void: 15 | stats = value 16 | 17 | if stats == null: 18 | return 19 | 20 | _stats = stats.duplicate(true) 21 | initialize_replication() 22 | 23 | ## Get a specific stat by it's key 24 | func get_stat(key: Variant) -> Stat: 25 | return _stats.get_stat(key) if _stats != null else null 26 | 27 | 28 | 29 | ### 30 | # STAT MODIFIERS 31 | ### 32 | 33 | ## Add a stat modifier to a stat 34 | func add_stat_modifier(stat_modifier: StatModifier) -> void: 35 | if _stats == null: 36 | return 37 | 38 | _stats.add_stat_modifier(stat_modifier) 39 | 40 | ## Remove a stat modifier from a stat 41 | func remove_stat_modifier(stat_modifier: StatModifier) -> void: 42 | if _stats == null: 43 | return 44 | 45 | _stats.remove_stat_modifier(stat_modifier) 46 | 47 | ## Clear all stat modifiers from all stats 48 | func clear_stat_modifiers() -> void: 49 | if _stats == null: 50 | return 51 | 52 | _stats.clear_stat_modifiers() 53 | 54 | 55 | ### 56 | # MULTIPLAYER (Experimental) 57 | # TODO: Make this a component to attach to a StatsManager 58 | ### 59 | func _ready() -> void: 60 | if !is_multiplayer_authority(): 61 | return 62 | 63 | initialize_replication() 64 | 65 | var is_replication_enabled: bool = false 66 | 67 | ## Initialize the replication of all stats to all peers 68 | func initialize_replication() -> void: 69 | if Engine.is_editor_hint(): 70 | return 71 | 72 | if is_replication_enabled == true or !is_node_ready(): 73 | return 74 | 75 | if _stats == null: 76 | return 77 | 78 | for stat_key in _stats.map.keys(): 79 | _stats.get_stat(stat_key).value_changed.connect(_on_stat_value_changed.bind(stat_key, _stats.get_stat(stat_key))) 80 | 81 | is_replication_enabled = true 82 | 83 | func _on_stat_value_changed(key: GDScript, stat: Stat) -> void: 84 | sync_stat.rpc(key.resource_path, stat.value) 85 | 86 | ## Sync the stat value of a specific stat by it's key and it's current value 87 | @rpc("call_local", "any_peer") 88 | func sync_stat(key_path, value) -> void: 89 | var key: GDScript = load(key_path) 90 | var stat: Stat = get_stat(key) 91 | 92 | if stat: 93 | stat.value = value 94 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/components/_status_effect_component.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_stat/icons/icons8-square-24.png") 2 | extends "res://addons/enhanced_stat/scripts/core/status_effects/status_effect_abstract.gd" 3 | class_name StatusEffectComponent 4 | 5 | func _enable() -> void: 6 | pass 7 | 8 | func _disable() -> void: 9 | pass 10 | 11 | func _tick() -> void: 12 | pass 13 | 14 | func get_intensity() -> float: 15 | return 0. -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/components/stat_modifier_status_effect_component.gd: -------------------------------------------------------------------------------- 1 | extends StatusEffectComponent 2 | class_name StatModifierStatusEffectComponent 3 | 4 | @export var stat_modifiers: Array[StatModifier] = [] 5 | 6 | 7 | var stat_manager: StatsManager 8 | 9 | func _enable() -> void: 10 | stat_manager = get_target_component(StatsManager) 11 | 12 | if stat_manager == null: 13 | return 14 | 15 | for stat_modifier in stat_modifiers: 16 | stat_manager.add_stat_modifier(stat_modifier) 17 | 18 | func _disable() -> void: 19 | if stat_manager == null: 20 | return 21 | 22 | for stat_modifier in stat_modifiers: 23 | stat_manager.remove_stat_modifier(stat_modifier) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/components/vfx_status_effect_component.gd: -------------------------------------------------------------------------------- 1 | extends StatusEffectComponent 2 | class_name VfxStatusEffectComponent 3 | 4 | @export var packed_vfx: PackedScene 5 | 6 | var vfx: Node 7 | 8 | func _enable() -> void: 9 | vfx = packed_vfx.instantiate() 10 | target.add_child(vfx) 11 | 12 | func _disable() -> void: 13 | if vfx == null: 14 | return 15 | 16 | target.remove_child(vfx) 17 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/status_effect.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_stat/icons/pulse_icon.png") 2 | extends "res://addons/enhanced_stat/scripts/core/status_effects/status_effect_abstract.gd" 3 | class_name StatusEffect 4 | 5 | signal _enabled 6 | signal _disabled 7 | 8 | 9 | @export var id: String 10 | 11 | var intensity: float: 12 | get = get_intensity 13 | 14 | func get_intensity() -> float: 15 | return 0. 16 | 17 | 18 | 19 | var is_enabled: bool = false 20 | 21 | @export var duration: float = 1. 22 | 23 | var _duration: float: 24 | set = set_duration 25 | 26 | func set_duration(value) -> void: 27 | _duration = value 28 | 29 | if _duration <= 0: 30 | disable() 31 | 32 | func reset() -> void: 33 | _duration = duration 34 | 35 | 36 | @export var tick_interval: float = 1. 37 | 38 | var tick_clock: float = .0 39 | 40 | 41 | @export var status_effect_components: Array[StatusEffectComponent] = [] 42 | var initial_status_effect_components: Array[StatusEffectComponent] = [] 43 | 44 | 45 | func is_target_valid(_target: Node) -> bool: 46 | return true 47 | 48 | func set_target(value) -> void: 49 | if not is_target_valid(value): 50 | return 51 | 52 | target = value 53 | 54 | 55 | func enable() -> void: 56 | if not is_target_valid(target): 57 | return 58 | 59 | is_enabled = true 60 | reset() 61 | _enable() 62 | _enabled.emit() 63 | 64 | func disable() -> void: 65 | if not is_enabled: 66 | return 67 | 68 | is_enabled = false 69 | _disable() 70 | _disabled.emit() 71 | 72 | func tick(delta: float) -> void: 73 | if not is_enabled: 74 | return 75 | 76 | _duration -= delta 77 | 78 | if tick_interval == 0: 79 | return _tick() 80 | 81 | if tick_interval <= -1: 82 | return 83 | 84 | tick_clock += delta 85 | 86 | if tick_clock < tick_interval: 87 | return 88 | 89 | tick_clock -= tick_interval 90 | _tick() 91 | 92 | 93 | func _enable() -> void: 94 | for status_effect_component in status_effect_components: 95 | var initial_status_effect_component = status_effect_component.duplicate(true) 96 | initial_status_effect_component.target = target 97 | initial_status_effect_component._enable() 98 | initial_status_effect_components.append(initial_status_effect_component) 99 | 100 | func _disable() -> void: 101 | for status_effect_component in initial_status_effect_components: 102 | status_effect_component._disable() 103 | 104 | func _tick() -> void: 105 | for status_effect_component in initial_status_effect_components: 106 | status_effect_component._tick() 107 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/status_effect_abstract.gd: -------------------------------------------------------------------------------- 1 | extends Resource 2 | 3 | var target: Node: 4 | set = set_target 5 | 6 | func set_target(value) -> void: 7 | target = value 8 | 9 | func _enable() -> void: 10 | pass 11 | 12 | func _disable() -> void: 13 | pass 14 | 15 | func _tick() -> void: 16 | pass 17 | 18 | func get_intensity() -> float: 19 | return 0. 20 | 21 | 22 | func get_target_component(component_class): 23 | return NodeUtils.find_node(target, component_class) 24 | 25 | func get_target_stat(stat_class): 26 | return NodeUtils.find_node(target, StatsManager).get_stat(stat_class) 27 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/status_effect_applier.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_stat/icons/ecg_icon.png") 2 | extends Node 3 | class_name StatusEffectApplier 4 | 5 | @export var status_effects: Array[StatusEffect] = [] 6 | 7 | func apply(target: Node) -> void: 8 | var status_effect_manager: StatusEffectManager = NodeUtils.find_node(target, StatusEffectManager) 9 | 10 | if status_effect_manager == null or status_effects == null: 11 | return 12 | 13 | for status_effect in status_effects: 14 | var instance: StatusEffect = status_effect.duplicate() 15 | instance.target = target 16 | 17 | status_effect_manager.add_status_effect(instance) 18 | 19 | 20 | 21 | func remove(target: Node) -> void: 22 | var status_effect_manager: StatusEffectManager = NodeUtils.find_node(target, StatusEffectManager) 23 | 24 | if status_effect_manager == null or status_effects == null: 25 | return 26 | 27 | for status_effect in status_effects: 28 | var instance: StatusEffect = status_effect.duplicate() 29 | 30 | status_effect_manager.remove_status_effect(instance) 31 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/core/status_effects/status_effect_manager.gd: -------------------------------------------------------------------------------- 1 | @icon("res://addons/enhanced_stat/icons/icons8-system-task-24.png") 2 | extends Node 3 | class_name StatusEffectManager 4 | 5 | signal _status_effect_added(status_effect: StatusEffect) 6 | signal _status_effect_removed(status_effect: StatusEffect) 7 | 8 | 9 | var status_effects: Dictionary = {} 10 | 11 | func add_status_effect(status_effect: StatusEffect) -> void: 12 | if status_effect == null: 13 | return 14 | 15 | if status_effects.has(status_effect.id): 16 | var current_status_effect = status_effects[status_effect.id] 17 | 18 | if status_effect.intensity == current_status_effect.intensity: 19 | status_effects[status_effect.id].reset() 20 | return 21 | 22 | if status_effect.intensity < current_status_effect.intensity: 23 | return 24 | 25 | remove_status_effect(current_status_effect) 26 | 27 | status_effects[status_effect.id] = status_effect 28 | status_effect.enable() 29 | status_effect._disabled.connect(remove_status_effect.bind(status_effect), CONNECT_ONE_SHOT) 30 | _status_effect_added.emit(status_effect) 31 | 32 | func remove_status_effect(status_effect: StatusEffect) -> void: 33 | if status_effect == null: 34 | return 35 | 36 | if not status_effects.has(status_effect.id): 37 | return 38 | 39 | status_effect.disable() 40 | status_effects.erase(status_effect.id) 41 | _status_effect_removed.emit(status_effect) 42 | 43 | func clear_status_effects() -> void: 44 | for status_effect in status_effects.values(): 45 | remove_status_effect(status_effect) 46 | 47 | 48 | func _physics_process(delta: float) -> void: 49 | for status_effect in status_effects.values(): 50 | status_effect.tick(delta) 51 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/editor/stats/bind/stat_bind_editor.gd: -------------------------------------------------------------------------------- 1 | extends EnhancedEditorInspectorPlugin 2 | 3 | 4 | func _can_handle(object: Object) -> bool: 5 | return object is AbstractStatBind 6 | 7 | func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: 8 | if name == "stat_key": 9 | var button = OptionProperties.new() 10 | button.initialize(object, name, _get_stats(object)) 11 | add_custom_editor_inspector_container("Stat", button) 12 | return true 13 | 14 | if name == "property_to_bind": 15 | var button = OptionProperties.new() 16 | button.initialize(object, name, _get_properties(object)) 17 | add_custom_editor_inspector_container("Property to bind", button) 18 | return true 19 | 20 | return false 21 | 22 | func _get_stats(stat_bind: AbstractStatBind) -> Array[Dictionary]: 23 | if stat_bind == null or stat_bind._stats_manager == null: 24 | return [] 25 | 26 | var properties: Array[Dictionary] = [] 27 | 28 | for property in ClassUtils.get_custom_classes_from_exported_properties(stat_bind._stats_manager).filter(func(p): return p.name == "stats"): 29 | for custom_class in ClassUtils.get_custom_classes_from_custom_class(property.hint_string): 30 | properties.append({ 31 | "name": custom_class.class.replace("Stat", ""), 32 | "icon": custom_class.icon, 33 | "value": load(custom_class.path) 34 | }) 35 | 36 | return properties 37 | 38 | func _get_properties(stat_bind: AbstractStatBind) -> Array[Dictionary]: 39 | if stat_bind == null or stat_bind.target == null: 40 | return [] 41 | 42 | var properties: Array[Dictionary] = [] 43 | 44 | for property in stat_bind.target.get_property_list().filter(func(p: Dictionary): return !p.name.contains(".gd")): 45 | properties.append({ 46 | "name": property.name, 47 | "value": property.name 48 | }) 49 | 50 | properties.reverse() 51 | return properties -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/editor/stats/listeners/stat_listener_button.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends OptionButton 3 | 4 | 5 | var stat_listener: StatListener 6 | 7 | func initialize(_stat_listener: StatListener) -> void: 8 | stat_listener = _stat_listener 9 | 10 | 11 | var items: Array[Dictionary] = [] 12 | 13 | func _ready() -> void: 14 | if stat_listener == null or stat_listener._stats_manager == null: 15 | return 16 | 17 | item_selected.connect(_on_item_selected) 18 | 19 | for property in ClassUtils.get_custom_classes_from_exported_properties(stat_listener._stats_manager): 20 | if property.name != "stats": 21 | continue 22 | 23 | var classes := ClassUtils.get_custom_classes_from_custom_class(property.hint_string) 24 | 25 | for i in len(classes): 26 | var custom_class = classes[i] 27 | var custom_class_name = custom_class.class.replace("Stat", "") 28 | add_item(custom_class_name, i) if custom_class.icon == "" else add_icon_item(load(custom_class.icon) as Texture2D, custom_class_name, i) 29 | items.append(custom_class) 30 | 31 | if stat_listener.stat_key != null and stat_listener.stat_key.resource_path == custom_class.path: 32 | select(i) 33 | 34 | 35 | 36 | 37 | func _on_item_selected(index: int) -> void: 38 | var custom_class: Dictionary = items[index] 39 | stat_listener.stat_key = load(custom_class.path) -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/editor/stats/listeners/stat_listener_editor.gd: -------------------------------------------------------------------------------- 1 | extends EnhancedEditorInspectorPlugin 2 | 3 | const StatListenerButton = preload("res://addons/enhanced_stat/scenes/editor/stats/listeners/StatListenerButton.tscn") 4 | 5 | func _can_handle(object: Object) -> bool: 6 | return object is StatListener 7 | 8 | 9 | func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: 10 | if name == "stat_key": 11 | var button = StatListenerButton.instantiate() 12 | button.initialize(object) 13 | add_custom_editor_inspector_container("Stat", button) 14 | return true 15 | 16 | return false -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/editor/stats/modifiers/stat_modifier_button.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends OptionButton 3 | 4 | 5 | var stat_modifier: StatModifier 6 | 7 | func initialize(_stat_modifier: StatModifier) -> void: 8 | stat_modifier = _stat_modifier 9 | 10 | 11 | var items: Array[Dictionary] = [] 12 | 13 | func _ready() -> void: 14 | if stat_modifier == null: 15 | return 16 | 17 | item_selected.connect(_on_item_selected) 18 | 19 | var custom_class_modifier: String = ClassUtils.get_custom_class(stat_modifier).class 20 | 21 | var classes := ClassUtils.get_inheriters_from_class(custom_class_modifier.replace("Modifier", "")) 22 | for i in len(classes): 23 | var custom_class = classes[i] 24 | var custom_class_name = custom_class.class.replace("Stat", "") 25 | 26 | add_item(custom_class_name, i) if custom_class.icon == "" else add_icon_item(load(custom_class.icon) as Texture2D, custom_class_name, i) 27 | 28 | items.append(custom_class) 29 | 30 | if stat_modifier.stat_key != null and stat_modifier.stat_key.resource_path == custom_class.path: 31 | select(i) 32 | _on_item_selected(i) 33 | 34 | if selected <= 0: 35 | select(0) 36 | _on_item_selected(0) 37 | 38 | func _on_item_selected(index: int) -> void: 39 | var custom_class: Dictionary = items[index] 40 | stat_modifier.stat_key = load(custom_class.path) 41 | -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/editor/stats/modifiers/stat_modifier_editor.gd: -------------------------------------------------------------------------------- 1 | extends EnhancedEditorInspectorPlugin 2 | 3 | const StatModifierButton = preload("res://addons/enhanced_stat/scenes/editor/stats/modifiers/StatModifierButton.tscn") 4 | 5 | func _can_handle(object: Object) -> bool: 6 | return object is StatModifier 7 | 8 | 9 | func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: 10 | if name == "stat_key": 11 | var button = StatModifierButton.instantiate() 12 | button.initialize(object) 13 | add_custom_editor_inspector_container("Stat", button) 14 | return true 15 | 16 | return false -------------------------------------------------------------------------------- /addons/enhanced_stat/scripts/utils/stat_utils.gd: -------------------------------------------------------------------------------- 1 | extends Object 2 | class_name StatUtils 3 | 4 | 5 | static func bind_stat(target: Object, property: String, stat_key: GDScript, stats_manager: StatsManager) -> void: 6 | if target == null or property == "": 7 | return push_error("Target has not been configured properly.") 8 | 9 | if stat_key == null or stats_manager == null: 10 | return push_error("Stat has not been configured properly.") 11 | 12 | var _stat: Stat = stats_manager.get_stat(stat_key) 13 | var target_value = target.get(property) 14 | 15 | if target_value != null and not target_value is Object: 16 | return push_error("Couldn't bind an object to a primitive type, did you meant to bind the value instead ?") 17 | 18 | if target_value == _stat: 19 | return 20 | 21 | target.set(property, _stat) 22 | 23 | static func unbind_stat(target: Object, property: String) -> void: 24 | target.set(property, null) 25 | 26 | static func bind_stat_value(target: Object, property: String, stat_key: GDScript, stats_manager: StatsManager): 27 | if target == null or property == "": 28 | push_error("Target has not been configured properly.") 29 | return null 30 | 31 | if stat_key == null or stats_manager == null: 32 | push_error("Stat has not been configured properly.") 33 | return null 34 | 35 | var target_value = target.get(property) 36 | if target_value != null and target_value is Object: 37 | push_error("Couldn't bind a primitive type to an object, did you meant to bind the stat instead ?") 38 | return null 39 | 40 | var _stat: Stat = stats_manager.get_stat(stat_key) 41 | assert(_stat != null) 42 | 43 | var _on_value_changed: Callable = \ 44 | func _on_value_changed() -> void: 45 | target.set(property, _stat.value) 46 | 47 | _stat.value_changed.connect(_on_value_changed) 48 | _on_value_changed.call() 49 | return _on_value_changed 50 | 51 | static func unbind_stat_value(target: Object, property: String, _on_value_changed: Callable) -> void: 52 | if _on_value_changed == null: 53 | return 54 | 55 | var _stat: Stat = target.get(property) 56 | 57 | if _stat == null: 58 | return push_warning("Can't unbind null _stat.") 59 | 60 | _stat.value_changed.disconnect(_on_value_changed) 61 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | [](https://godotengine.org) 4 | [](https://opensource.org/licenses/MIT) 5 | [](https://zennyth.github.io/EnhancedStat/) 6 | 7 | **Enhanced Stat** is an addon suite for Godot 4.1, designed to provide an intuitive and powerful way to manage stats in games such as Health, Mana, Speed, and Attack Damage. This addon is part of the **Enhanced** suite aimed to make your Godot development faster, cleaner, and more enjoyable! 8 | 9 | # 🚀 Features 10 | 11 | ## 📈 Stats 12 | 13 | - **Reactive Programming**: Stats are reactive, and changes can be listened to in real-time. 14 | - **Computed Stats**: Type safe basic stats. 15 | - **Aggregated Stats**: Create new stats based on existing stats, automatically updating when dependencies change. 16 | - **Bind Mechanism**: Allows easy binding of stats to in game nodes. 17 | - **Modifiers**: Flexible modification of stats through addition, multiplication, or other custom methods. 18 | - **Listeners**: Events to notify game logic of stats changes. 19 | 20 | ## ⚡ Status Effects 21 | 22 | - **Component-based**: Reusable components that can be combined to create complex status effects. 23 | - **Modifiers**: Attachable to stats to create temporary or permanent changes. 24 | - **Vfx**: Add vfxs or nodes while the status effect is active. 25 | 26 | ### 💻 Editor Friendly 27 | 28 | - **Editor Inspector**: Use the editor to configure the stats you need for your game, the status effects and more ... 29 | 30 | 31 | # 📦 Installation 32 | 33 | 1. Clone or download the repository. 34 | 2. Copy the `enhanced_stat` and `enhanced_common` folders inside your project's `addons` directory. 35 | 3. Enable the addon by going to `Project -> Project Settings -> Plugins` and activating the "Enhanced Stat" and "Enhanced Common" plugin. 36 | 37 | # 🍻 Contribute 38 | 39 | Do you want to contribute? Learn more in [the contribution section](/contribute.md). 40 | 41 | # 🥰 Credits 42 | 43 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [⚡ Getting started](/) 2 | * [📗 Manual](/manual/main.md) 3 | * [Managing Stats](/manual/stats/main.md) 4 | * [Creating Stats](/manual/stats/create.md) 5 | * [Computed Stats](/manual/stats/computed.md) 6 | * [Aggregated Stats](/manual/stats/aggregated.md) 7 | * [Binding Stats](/manual/stats/bind.md) 8 | * [Stat Modifiers](/manual/stats/modifiers.md) 9 | * [Status Effects](/manual/status_effects/main.md) 10 | * [🍻 Contribute](/contribute.md) 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/logo.png -------------------------------------------------------------------------------- /docs/assets/stat_binding_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/stat_binding_configuration.png -------------------------------------------------------------------------------- /docs/assets/stat_binding_dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/stat_binding_dropdown.png -------------------------------------------------------------------------------- /docs/assets/stats_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/stats_manager.png -------------------------------------------------------------------------------- /docs/assets/status_effect_applier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/status_effect_applier.png -------------------------------------------------------------------------------- /docs/assets/stun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/stun.png -------------------------------------------------------------------------------- /docs/assets/stun_files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zennyth/EnhancedStat/98bee9ca094c625defa5543cfa17a1e88c42fdb7/docs/assets/stun_files.png -------------------------------------------------------------------------------- /docs/contribute.md: -------------------------------------------------------------------------------- 1 | # 🍻 Contributing to this project 2 | 3 | If you'd like to suggest improvements to this add-on or fix issues, you can raise a pull request or [raise an issue](https://github.com/zennyth/enhanced_stat/issues). 4 | 5 | ## 🧪 Unit testing 6 | 7 | This project will use [gdUnit](https://github.com/MikeSchulze/gdUnit4) to ensure code quality. Every pull request that introduces new changes, such as nodes or additional methods, must also provide some unit tests inside the `test/` folder. Make sure your test is in the correct folder. 8 | 9 | ## ⚡ Adding a new node 10 | 11 | If you want to introduce a new node, raise a pull request after checking the issues tab for any discussions on new nodes. It's a great place to gather feedback before implementing a new node. Ensure that you also introduce an icon for your node that follows the color scheme: 12 | 13 | - Control: `#A5EFAC` 14 | - Node2D: `#A3B4F0` 15 | - Utility Node and Resource: `#E0E0E0` 16 | 17 | Also, update the `README.md` file with the documentation for the newly introduced node. 18 | 19 | ## 📚 Adding documentation 20 | 21 | If you're introducing a new feature or changing behavior, update the wiki accordingly. Modify the `/docs` folder inside the repository. To test your wiki locally, run the following command: 22 | ```bash 23 | docsify serve ./docs 24 | ``` 25 | > 💡 [Learn more](https://docsify.js.org/#/?id=docsify) about how to use **docsify**. -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |