Gate Abilities with Runtime State
Block ability activation using cooldown tags, resources, and target checks.
Implement AbilityActivationGate when activation depends on runtime state.
The RPG gate checks:
- whether a matching cooldown effect is active
- whether the owner has enough mana
- whether the target still exists
Return AbilityActivationDecision::Block(reason) for expected gameplay
failure. Return an error for runtime failures that should be handled
differently.
Block reasons are caller-owned. They can become command responses, UI messages, logs, scripting facts, or network errors after the activation command returns.
The gate should inspect runtime state, not perform the ability's behavior. In the RPG runtime, activation is blocked when a cooldown tag is active, mana is too low, or the target object has already been destroyed:
impl AbilityActivationGate<CombatState, CombatTags, AbilityPayload> for CombatGate {
type Error = CombatError;
type BlockReason = CombatBlockReason;
fn can_activate(
&mut self,
context: &CombatState,
attempt: AbilityActivationAttemptView<'_, CombatTags, AbilityPayload>,
) -> Result<AbilityActivationDecision<Self::BlockReason>, Self::Error> {
if context
.cooldowns
.has_tag(attempt.owner_id, &attempt.payload.cooldown_tag())
{
return Ok(AbilityActivationDecision::Block(CombatBlockReason::OnCooldown));
}
if context.mana(attempt.owner_id) < attempt.payload.mana_cost() {
return Ok(AbilityActivationDecision::Block(CombatBlockReason::NotEnoughMana));
}
if let Some(target) = attempt.payload.target()
&& !context.objects.exists(target)
{
return Ok(AbilityActivationDecision::Block(CombatBlockReason::InvalidTarget));
}
Ok(AbilityActivationDecision::Allow)
}
}Keep the returned block reason stable enough for adapters. A UI can translate
OnCooldown into disabled-button state, while a server can translate the same
reason into a command response.
Use this with: