# `godot-gameplay-abilities` Plugin Documentation ## Core Concepts - `Ability`: A single ability that can be activated, granted, revoked, etc. Works like a blueprint of an ability. - `AbilityContainer`: A container that holds abilities. It can be attached to any node in the scene. - `RuntimeAbility`: The runtime representation of an ability. It is created when an ability is added to an `AbilityContainer`. ## Designing Abilities by Extending the Ability Script ### Ability States #### Classification Logically, abilities can be divided into the following states: - **granted**: The ability has been granted to the container and is available. - **ended**: A stable state after the ability has finished execution. The ability is also in this state when first granted but not yet activated (i.e., the *initial state*). - **active**: The ability is currently being executed. - **duration_active**: The duration period after the ability is activated. - **cooldown_active**: The cooldown phase after the ability is used. - **blocked**: The ability is temporarily disabled. By default, the cooldown and duration are reset to 0.0. - **revoked**: The ability has been revoked and must be granted again to be used. #### Transitions - State Transition Diagram: ```mermaid stateDiagram-v2 [*] --> granted revoked --> granted : grant() granted --> revoked : revoke() granted --> blocked : block() granted --> active : activate() active --> blocked : block() active --> duration_active : duration time > 0 duration_active --> cooldown_active : duration time end
& cooldown time > 0 cooldown_active --> ended : cooldown time end cooldown_active --> blocked : block() blocked --> granted : unblock() blocked --> revoked : revoke() ended --> active : activate() ended --> blocked : block() ended --> revoked : revoke() ``` - Activation State Cycle: ```mermaid graph TD ended -->|activate| active active -->|duration_time > 0| duration_active duration_active -->|duration_time end| cooldown_active active -->|duration_time = 0| cooldown_active cooldown_active -->|cooldown_time end| ended ``` - Blocked State Cycle: ```mermaid graph LR revoked -- grant --> ended ended -- block --> blocked active -- block --> blocked duration_active -- block --> blocked cooldown_active -- block --> blocked blocked -- unblock --> ended ``` ### Ability Implementation: Overriding the Ability Class The `Ability` class provides several types of virtual methods that can be overridden as needed. Before overriding, it's important to understand their purposes and the implications of overriding: - `_can_*`: Condition checks that determine whether an operation is allowed. - `_should_*`: Behavioral judgments that determine whether certain actions should be automatically performed (e.g., auto-activation, auto-ending). - `_on_*`: Event callbacks that execute custom logic when the state changes. - `_get_*`: Getter methods for cooldown and duration properties. #### `_can_*` Methods `_can_*` methods perform **condition validation** before any state transition to determine whether an operation is allowed. Overridden methods should return a boolean indicating whether the condition is met. If not overridden, no condition check is performed (equivalent to returning `true` by default). - `_can_activate_cooldown`: Checks if the cooldown can be started. - `_can_be_activated`: Checks if the ability can be activated. - `_can_be_blocked`: Checks if the ability can be blocked. - `_can_be_ended`: Checks if the ability can be ended. - `_can_be_granted`: Checks if the ability can be granted. - `_can_be_revoked`: Checks if the ability can be revoked. #### `_should_*` Methods `_should_*` methods implement **automated state management** by checking if a state transition should be triggered at specific times. | Method | Call Timing | Frequency | Function Description | Default Return Value | |-------------------------|-------------------------------|-----------------|--------------------------------------------------|----------------------| | `_should_be_activated` | `handle_tick()` | On ability add + every frame | Checks if the ability should be auto-reactivated | `false` | | `_should_be_blocked` | `handle_tick()`, `activate()`, `unblock()` | Every frame + on activation + on unblocking | Checks if the ability should enter the blocked state | `false` | | `_should_be_ended` | `handle_tick()` | Every frame | Checks if the ability should end | `true` | | `_should_reset_cooldown`| `block()`, `end()` | During state transitions | Determines if the cooldown should be reset | `cooldown time != 0` | | `_should_reset_duration`| `block()`, `end()` | During state transitions | Determines if the duration should be reset | `true` | Note: When the container calls `add_ability`, it automatically checks if the ability needs to be activated. #### `_on_*` Methods `_on_*` methods provide event hooks that allow developers to execute custom logic when an ability's state changes. | Method | Call Timing | Primary Purpose | |----------------|---------------------------------|---------------------------------------------------------------------------------| | `_on_activate` | After the ability is successfully activated | Execute activation logic (play effects, apply buffs, etc.) | | `_on_block` | After the ability is blocked | Handle blocking state (show disabled icon, interrupt channeling, etc.) | | `_on_end` | After the ability normally ends | Clean up skill effects (remove buffs, calculate damage, etc.) | | `_on_grant` | When the ability is granted | Initialize the skill (load resources, register listeners, etc.) | | `_on_revoke` | When the ability is revoked | Fully clean up the skill (release resources, remove states, etc.) | | `_on_tick` | Every `physics` frame update (`handle_tick()`) | Implement fully custom ability logic, overriding default mechanisms | - **Note**: - **Caution:override `_on_tick`**: Overriding this method completely disables default duration and cooldown mechanisms, as well as per-frame `_should_*` checks! - **Avoid recursive calls**: Do not trigger the same event in callback methods (e.g., calling `activate` in `_on_activate`), which may cause infinite recursion. - **State safety**: Be aware of the ability's possible states in callback methods. For example, in `_on_end`, the ability is about to enter the ended state, so activation attempts should be avoided. - Additionally, the ability's state in callback methods is still the previous state, not yet fully transitioned. #### `_get_*` Methods Dynamically retrieve the ability's cooldown and duration. When the ability is activated, `_get_cooldown` and `_get_duration` are called to obtain these values. ## Using Abilities with the `AbilityContainer` Class The `AbilityContainer` class manages a collection of abilities and provides methods to operate on them. #### Ability Management: Add, Remove, Modify, Query | Method Signature | Return Type | Method Description | |------------------|-------------|--------------------| | `add_ability(ability: Ability)` | `bool` | Adds an ability resource to the container. Automatically calls:
- `try_grant(ability)`
- If the ability is granted and `should_be_activated`, calls `try_activate(ability)` | | `find_ability(predicate: Callable) const` | `RuntimeAbility` | Finds an ability in the container by calling a predicate function | | `get_runtime_abilities() const` | `Array[RuntimeAbility]` | Gets all runtime ability instances | | `get_runtime_ability(ability_name_or_instance: Variant) const` | `RuntimeAbility` | Gets the ability by name or instance | | `has_ability(ability_name_or_instance: Variant) const` | `bool` | Checks if the container has the specified ability (parameter can be ability name or instance) | | `remove_ability(ability: Ability)` | `bool` | Ends the ability if active, revokes it, and removes the ability resource from the container | #### Ability State Checks | Method Signature | Return Type | Method Description | |------------------|-------------|--------------------| | `is_ability_active(ability_name_or_instance: Variant) const` | `bool` | Checks if the specified ability is active | | `is_ability_blocked(ability_name_or_instance: Variant) const` | `bool` | Checks if the specified ability is blocked | | `is_ability_cooldown_active(ability_name_or_instance: Variant) const` | `bool` | Checks if the specified ability's cooldown is active | | `is_ability_ended(ability_name_or_instance: Variant) const` | `bool` | Checks if the specified ability has ended | | `is_ability_granted(ability_name_or_instance: Variant) const` | `bool` | Checks if the specified ability has been granted | #### Ability Operations | Method Signature | Return Type | Method Description | |------------------|-------------|--------------------| | `try_activate(ability_or_ability_name: Variant) const` | `abilities.AbilityEventType` | Tries to activate the ability. If `_try_activate` is overridden, its logic controls activation. Emits `ability_activated` signal on success | | `try_block(ability_or_ability_name: Variant) const` | `abilities.AbilityEventType` | Tries to block the ability. If `_try_block` is overridden, its logic controls blocking. Emits `ability_blocked` signal on success | | `try_end(ability_or_ability_name: Variant) const` | `abilities.AbilityEventType` | Tries to end the ability. If `_try_end` is overridden, its logic controls ending. Emits `ability_ended` signal on success | | `try_grant(ability_or_ability_name: Variant) const` | `abilities.AbilityEventType` | Tries to grant the ability. If `_try_grant` is overridden, its logic controls granting. Emits `ability_granted` signal on success | | `try_revoke(ability_or_ability_name: Variant) const` | `abilities.AbilityEventType` | Tries to revoke the ability. If `_try_revoke` is overridden, its logic controls revocation. Emits `ability_revoked` signal on success | | `try_unblock(ability_or_ability_name: Variant) const` | `abilities.AbilityEventType` | Tries to unblock the ability. Emits `ability_unblocked` signal on success | ## Practical Applications ### Ability Design Example: Creating an Ability that Triggers Actions at Intervals During Its Duration This example uses Tween to achieve the effect. Theoretically, you could also override `_on_tick`, but this would disrupt default logic: ```gdscript extends Ability class_name TweenIntervalTriggeringAbility ## Ability that triggers at regular intervals using Tween # Ability name const ABILITY_NAME := "TweenIntervalTriggeringAbility" var ability_tween: Tween var cooldown_time := 2.0 var duration_time := 5.0 var set_delay_time := 1 func _init(_ability_name = ABILITY_NAME): ability_name = _ability_name print(ABILITY_NAME + "::_init - ability_name: %s" % ability_name) # Get ability cooldown time func _get_cooldown(_ability_container: AbilityContainer) -> float: print(ABILITY_NAME + "::_get_cooldown - return: %s" % cooldown_time) return cooldown_time # Get ability duration func _get_duration(_ability_container: AbilityContainer) -> float: print(ABILITY_NAME + "::_get_duration - return: %s" % duration_time) return duration_time # Called when the ability is granted func _on_grant(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: print(ABILITY_NAME + "::_on_grant ") var test_print_times := 0 # Called when the ability is activated func _on_activate(ability_container: AbilityContainer, runtime_ability: RuntimeAbility) -> void: print(ABILITY_NAME + "::_on_activate ") if ability_tween: ability_tween.kill() @warning_ignore("narrowing_conversion") ability_tween = ability_container.create_tween().set_loops(duration_time/set_delay_time) ability_tween.tween_callback( func(): print("ability_container.get_runtime_abilities(): ", ability_container.get_runtime_abilities()) print("runtime_ability.is_active(): ", runtime_ability.is_active()) test_print_times += 1 print("Interval Triggering %s" % test_print_times) ).set_delay(set_delay_time) # Called when the ability is blocked func _on_block(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: print(ABILITY_NAME + "::_on_block ") if ability_tween: ability_tween.kill() # Called when the ability ends func _on_end(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: print(ABILITY_NAME + "::_on_end ") if ability_tween: ability_tween.kill() # Called when the ability is revoked func _on_revoke(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: print(ABILITY_NAME + "::_on_revoke ") if ability_tween: ability_tween.kill() ``` ### Ability Container Usage Example ```gdscript extends Node func _ready() -> void: # Add ability var test_ability = TweenIntervalTriggeringAbility.new() var container = AbilityContainer.new() add_child(container) container.add_ability(test_ability) # Activate ability container.try_activate(test_ability.ABILITY_NAME) # Check ability state if container.is_ability_active(test_ability.ABILITY_NAME): print("Ability is active") # Remove ability container.remove_ability(test_ability) ``` ### Ability Template Reference ```gdscript class_name BaseAbility extends Ability # Ability name (should be unique) const ABILITY_NAME := "BaseAbility" #region Initialization methods func _init(_ability_name = ABILITY_NAME): ability_name = _ability_name #endregion #region Event callback methods # These methods are called when the state changes func _on_activate(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: # Logic when the ability is activated # Typically execute the main effect of the ability here pass func _on_block(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: # Logic when the ability is blocked # Typically interrupt the ability effect here pass func _on_end(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: # Logic when the ability ends # Typically clean up the ability effect here pass func _on_grant(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: # Logic when the ability is granted # Typically initialize the ability here pass func _on_revoke(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> void: # Logic when the ability is revoked # Typically perform complete cleanup of the ability here pass #endregion #region Condition check methods # These methods determine whether state transitions are allowed func _can_activate_cooldown(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> bool: # Default: Always allow cooldown activation return true func _can_be_activated(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> bool: # Default: Always allow ability activation return true func _can_be_blocked(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> bool: # Default: Always allow ability blocking return true func _can_be_granted(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> bool: # Default: Always allow ability granting return true func _can_be_revoked(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> bool: # Default: Always allow ability revocation return true func _can_be_ended(_ability_container: AbilityContainer, _runtime_ability: RuntimeAbility) -> bool: # Default: Always attempt to end (if conditions permit) return true #endregion #region Property retrieval methods # These methods provide dynamic properties of the ability func _get_cooldown(_ability_container: AbilityContainer) -> float: # Default: No cooldown time return 0.0 func _get_duration(_ability_container: AbilityContainer) -> float: # Default: No duration return 0.0 #endregion #region Automatic state management methods # These methods implement automated state management func _should_be_activated(_ability_container: AbilityContainer) -> bool: # Default: Do not auto-activate return false func _should_be_blocked(_ability_container: AbilityContainer) -> bool: # Default: Do not auto-block return false func _should_be_ended(_ability_container: AbilityContainer) -> bool: # Default: Always try to end (if conditions allow) return true #endregion #region Methods for cautious use # Override this method with caution # Overriding this method transfers full control of ticking behavior to the implementer, bypassing the ability system's duration/cooldown handling and resulting in no calls to the per-frame `_should_*` method checks. #func _on_tick(delta: float, cooldown_time: float, ability_container: AbilityContainer, runtime_ability: RuntimeAbility) -> void: #pass #endregion ```