Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions Forge.Tests/Statescript/Nodes/GrantAbilityNodesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void Grant_ability_node_grants_while_active_and_revokes_on_deactivation()

var grantNode = new GrantAbilityNode();
grantNode.BindInput(GrantAbilityNode.AbilityDataInput, "abilityData");
grantNode.BindInput(GrantAbilityNode.TargetInput, "target");
grantNode.BindInput(GrantAbilityNode.EntityInput, "target");
grantNode.BindOutput(GrantAbilityNode.AbilityOutput, "grantedAbility");

graph.AddNode(grantNode);
Expand All @@ -60,7 +60,7 @@ public void Grant_ability_node_grants_while_active_and_revokes_on_deactivation()

[Fact]
[Trait("Graph", "GrantAbility")]
public void Grant_ability_node_can_activate_the_granted_ability()
public void Grant_ability_node_output_feeds_try_activate_ability_node()
{
var owner = new TestEntity(_tagsManager, _cuesManager);

Expand All @@ -78,15 +78,24 @@ public void Grant_ability_node_can_activate_the_granted_ability()
var graph = new Graph();
graph.VariableDefinitions.DefineObjectVariable("abilityData", abilityData);
graph.VariableDefinitions.DefineObjectVariable<IForgeEntity>("target", owner);
graph.VariableDefinitions.DefineObjectVariable<AbilityHandle>("grantedAbility");

var grantNode = new GrantAbilityNode(tryActivateOnGrant: true);
var grantNode = new GrantAbilityNode();
grantNode.BindInput(GrantAbilityNode.AbilityDataInput, "abilityData");
grantNode.BindInput(GrantAbilityNode.TargetInput, "target");
grantNode.BindInput(GrantAbilityNode.EntityInput, "target");
grantNode.BindOutput(GrantAbilityNode.AbilityOutput, "grantedAbility");

var activateNode = new TryActivateAbilityNode();
activateNode.BindInput(TryActivateAbilityNode.AbilityInput, "grantedAbility");

graph.AddNode(grantNode);
graph.AddNode(activateNode);
graph.AddConnection(new Connection(
graph.EntryNode.OutputPorts[EntryNode.OutputPort],
grantNode.InputPorts[StateNode<GrantAbilityNodeContext>.InputPort]));
graph.AddConnection(new Connection(
grantNode.OutputPorts[StateNode<GrantAbilityNodeContext>.OnActivatePort],
activateNode.InputPorts[ConditionNode.InputPort]));

var processor = new GraphProcessor(graph);
processor.StartGraph();
Expand All @@ -107,7 +116,7 @@ public void Grant_ability_permanently_node_grants_beyond_the_graph_lifetime()

var grantNode = new GrantAbilityPermanentlyNode();
grantNode.BindInput(GrantAbilityPermanentlyNode.AbilityDataInput, "abilityData");
grantNode.BindInput(GrantAbilityPermanentlyNode.TargetInput, "target");
grantNode.BindInput(GrantAbilityPermanentlyNode.EntityInput, "target");

graph.AddNode(grantNode);
graph.AddConnection(new Connection(
Expand Down
18 changes: 9 additions & 9 deletions Forge/Statescript/Nodes/Action/GrantAbilityPermanentlyNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ namespace Gamesmiths.Forge.Statescript.Nodes.Action;
/// <para>Permanent grants cannot be revoked or inhibited, use them for unlock-style progression. For grants tied to
/// a graph state's lifetime, use the GrantAbility state node instead; for data-driven grants, use effects with a
/// grant-ability component.</para>
/// <para>The ability-data input must resolve to an <see cref="AbilityData"/>. The target input defaults to the
/// ability context's owner when unbound. The level input defaults to the ability context's level, or <c>1</c>
/// without a context.</para>
/// <para>The ability-data input must resolve to an <see cref="AbilityData"/>. The entity input selects who receives
/// the grant, defaulting to the ability context's owner when unbound. The level input defaults to the ability
/// context's level, or <c>1</c> without a context.</para>
/// <para>The granted <see cref="AbilityHandle"/> is written to the node's output variable.</para>
/// </remarks>
/// <param name="levelOverridePolicy">When the ability is already granted, which level relationships override the
Expand All @@ -29,7 +29,7 @@ public class GrantAbilityPermanentlyNode(LevelComparison levelOverridePolicy = L
/// <summary>
/// Input property index for the entity to grant the ability on.
/// </summary>
public const byte TargetInput = 1;
public const byte EntityInput = 1;

/// <summary>
/// Input property index for the ability level.
Expand All @@ -55,7 +55,7 @@ public class GrantAbilityPermanentlyNode(LevelComparison levelOverridePolicy = L
protected override void DefineParameters(List<InputProperty> inputProperties, List<OutputVariable> outputVariables)
{
inputProperties.Add(new InputProperty("Ability Data", typeof(AbilityData)));
inputProperties.Add(new InputProperty("Target", typeof(IForgeEntity)));
inputProperties.Add(new InputProperty("Entity", typeof(IForgeEntity)));
inputProperties.Add(new InputProperty("Level", typeof(int)));
inputProperties.Add(new InputProperty("Source", typeof(IForgeEntity)));
outputVariables.Add(new OutputVariable("Ability", typeof(AbilityHandle)));
Expand All @@ -72,11 +72,11 @@ protected override void Execute(GraphContext graphContext)
return;
}

IForgeEntity? target = AbilityNodeUtilities.ResolveEntityOrOwner(
IForgeEntity? entity = AbilityNodeUtilities.ResolveEntityOrOwner(
graphContext,
InputProperties[TargetInput].BoundName);
InputProperties[EntityInput].BoundName);

if (target is null)
if (entity is null)
{
return;
}
Expand All @@ -86,7 +86,7 @@ protected override void Execute(GraphContext graphContext)
graphContext,
InputProperties[SourceInput].BoundName);

AbilityHandle handle = target.Abilities.GrantAbilityPermanently(
AbilityHandle handle = entity.Abilities.GrantAbilityPermanently(
abilityData,
level,
_levelOverridePolicy,
Expand Down
30 changes: 11 additions & 19 deletions Forge/Statescript/Nodes/State/GrantAbilityNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,21 @@ namespace Gamesmiths.Forge.Statescript.Nodes.State;
/// on deactivation.
/// </summary>
/// <remarks>
/// <para>The ability-data input must resolve to an <see cref="AbilityData"/>. The target input selects the entity to
/// grant on, defaulting to the ability context's owner when unbound. The level input defaults to the ability
/// <para>The ability-data input must resolve to an <see cref="AbilityData"/>. The entity input selects who receives
/// the grant, defaulting to the ability context's owner when unbound. The level input defaults to the ability
/// context's level, or <c>1</c> without a context. The optional source input records the granting entity.</para>
/// <para>The granted <see cref="AbilityHandle"/> is written to the node's output variable, so the graph can activate
/// or inspect the granted ability.</para>
/// or inspect the granted ability, for example through a <see cref="Condition.TryActivateAbilityNode"/>.</para>
/// <para>Grants are reference counted per source: if other grant sources (such as effects) also granted the same
/// ability, removing this node's grant only removes its own share.</para>
/// </remarks>
/// <param name="removalPolicy">How the ability is removed when the node deactivates. Defaults to
/// <see cref="AbilityDeactivationPolicy.CancelImmediately"/>.</param>
/// <param name="levelOverridePolicy">When the ability is already granted, which level relationships override the
/// existing level.</param>
/// <param name="tryActivateOnGrant">Whether to try activating the ability right after granting it.</param>
public class GrantAbilityNode(
AbilityDeactivationPolicy removalPolicy = AbilityDeactivationPolicy.CancelImmediately,
LevelComparison levelOverridePolicy = LevelComparison.None,
bool tryActivateOnGrant = false) : StateNode<GrantAbilityNodeContext>
LevelComparison levelOverridePolicy = LevelComparison.None) : StateNode<GrantAbilityNodeContext>
{
/// <summary>
/// Input property index for the ability data to grant.
Expand All @@ -36,7 +34,7 @@ public class GrantAbilityNode(
/// <summary>
/// Input property index for the entity to grant the ability on.
/// </summary>
public const byte TargetInput = 1;
public const byte EntityInput = 1;

/// <summary>
/// Input property index for the ability level.
Expand All @@ -55,7 +53,6 @@ public class GrantAbilityNode(

private readonly AbilityDeactivationPolicy _removalPolicy = removalPolicy;
private readonly LevelComparison _levelOverridePolicy = levelOverridePolicy;
private readonly bool _tryActivateOnGrant = tryActivateOnGrant;

/// <inheritdoc/>
public override string Description => "Grants an ability while active, removing the grant on deactivation.";
Expand All @@ -64,7 +61,7 @@ public class GrantAbilityNode(
protected override void DefineParameters(List<InputProperty> inputProperties, List<OutputVariable> outputVariables)
{
inputProperties.Add(new InputProperty("Ability Data", typeof(AbilityData)));
inputProperties.Add(new InputProperty("Target", typeof(IForgeEntity)));
inputProperties.Add(new InputProperty("Entity", typeof(IForgeEntity)));
inputProperties.Add(new InputProperty("Level", typeof(int)));
inputProperties.Add(new InputProperty("Source", typeof(IForgeEntity)));
outputVariables.Add(new OutputVariable("Ability", typeof(AbilityHandle)));
Expand All @@ -86,11 +83,11 @@ protected override void OnActivate(GraphContext graphContext)
return;
}

IForgeEntity? target = AbilityNodeUtilities.ResolveEntityOrOwner(
IForgeEntity? entity = AbilityNodeUtilities.ResolveEntityOrOwner(
graphContext,
InputProperties[TargetInput].BoundName);
InputProperties[EntityInput].BoundName);

if (target is null)
if (entity is null)
{
return;
}
Expand All @@ -102,23 +99,18 @@ protected override void OnActivate(GraphContext graphContext)

var grantSource = new StatescriptGrantSource(_removalPolicy, AbilityDeactivationPolicy.Ignore);

AbilityHandle handle = target.Abilities.GrantAbility(
AbilityHandle handle = entity.Abilities.GrantAbility(
abilityData,
level,
_levelOverridePolicy,
grantSource,
source);

nodeContext.GrantedHandle = handle;
nodeContext.GrantedOn = target;
nodeContext.GrantedOn = entity;
nodeContext.GrantSource = grantSource;

AbilityNodeUtilities.WriteHandleOutput(graphContext, OutputVariables[AbilityOutput], handle);

if (_tryActivateOnGrant)
{
handle.Activate(out _);
}
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ new GrantAbilityPermanentlyNode(levelOverridePolicy = LevelComparison.None)
| Index | Label | Type | Description |
|-------|-------|------|-------------|
| 0 | Ability Data | `AbilityData` | The ability to grant. |
| 1 | Target | `IForgeEntity` | Optional. The entity to grant on. Defaults to the ability context's owner. |
| 1 | Entity | `IForgeEntity` | Optional. The entity to grant on. Defaults to the ability context's owner. |
| 2 | Level | `int` | Optional. The grant level. Defaults to the ability context's level, or `1`. |
| 3 | Source | `IForgeEntity` | Optional. The granting source entity. |

Expand All @@ -50,7 +50,7 @@ new GrantAbilityPermanentlyNode(levelOverridePolicy = LevelComparison.None)

## Behavior

1. Resolves the ability data, target (default owner), level (default context level), and optional source.
1. Resolves the ability data, entity (default owner), level (default context level), and optional source.
2. Calls `EntityAbilities.GrantAbilityPermanently(...)`.
3. Writes the resulting `AbilityHandle` to the **Ability** output when bound.

Expand Down
10 changes: 5 additions & 5 deletions docs/statescript/nodes/state/grant-ability-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ Standard state ports (the grant/revoke happens on activate/deactivate).
```csharp
new GrantAbilityNode(
removalPolicy = AbilityDeactivationPolicy.CancelImmediately,
levelOverridePolicy = LevelComparison.None,
tryActivateOnGrant = false)
levelOverridePolicy = LevelComparison.None)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| removalPolicy | `AbilityDeactivationPolicy` | How the ability is removed when the node deactivates. |
| levelOverridePolicy | `LevelComparison` | When the ability is already granted, which level relationships override the existing level. |
| tryActivateOnGrant | `bool` | Whether to try activating the ability right after granting it. |

## Parameters

Expand All @@ -34,7 +32,7 @@ new GrantAbilityNode(
| Index | Label | Type | Description |
|-------|-------|------|-------------|
| 0 | Ability Data | `AbilityData` | The ability to grant. |
| 1 | Target | `IForgeEntity` | Optional. The entity to grant on. Defaults to the ability context's owner. |
| 1 | Entity | `IForgeEntity` | Optional. The entity to grant on. Defaults to the ability context's owner. |
| 2 | Level | `int` | Optional. The grant level. Defaults to the context level, or `1`. |
| 3 | Source | `IForgeEntity` | Optional. The granting source entity. |

Expand All @@ -46,9 +44,11 @@ new GrantAbilityNode(

## Behavior

1. On activation, grants the ability through the internal grant-source machinery (the same path effects use), optionally activating it, and writes the handle to the **Ability** output.
1. On activation, grants the ability through the internal grant-source machinery (the same path effects use) and writes the handle to the **Ability** output.
2. On deactivation, removes this node's grant according to `removalPolicy`.

To activate the granted ability, feed the **Ability** output into a [TryActivateAbilityNode](../condition/try-activate-ability-node.md).

Comment thread
lextatic marked this conversation as resolved.
## Usage

```csharp
Expand Down
Loading