688 lines
15 KiB
Markdown
688 lines
15 KiB
Markdown
|
|
# Decorators
|
||
|
|
|
||
|
|
Decorators are special nodes that wrap a single child and modify its execution behavior. They enable timing control, retry logic, conditional execution, and result inversion without modifying the child node itself.
|
||
|
|
|
||
|
|
## What Are Decorators?
|
||
|
|
|
||
|
|
Decorators sit between a parent and child node, transforming the child's behavior:
|
||
|
|
|
||
|
|
```
|
||
|
|
Parent
|
||
|
|
└─ Decorator
|
||
|
|
└─ Child
|
||
|
|
```
|
||
|
|
|
||
|
|
The decorator intercepts the child's execution, potentially:
|
||
|
|
- Repeating it multiple times
|
||
|
|
- Timing it out
|
||
|
|
- Inverting its result
|
||
|
|
- Guarding its execution
|
||
|
|
- Forcing a specific result
|
||
|
|
|
||
|
|
## Decorator Types
|
||
|
|
|
||
|
|
Storybook provides 10 decorator types:
|
||
|
|
|
||
|
|
| Decorator | Purpose | Example |
|
||
|
|
|-----------|---------|---------|
|
||
|
|
| `repeat` | Loop infinitely | `repeat { Patrol }` |
|
||
|
|
| `repeat(N)` | Loop N times | `repeat(3) { Check }` |
|
||
|
|
| `repeat(min..max)` | Loop min to max times | `repeat(2..5) { Search }` |
|
||
|
|
| `invert` | Flip success/failure | `invert { IsEnemy }` |
|
||
|
|
| `retry(N)` | Retry on failure (max N times) | `retry(5) { Open }` |
|
||
|
|
| `timeout(duration)` | Fail after duration | `timeout(10s) { Solve }` |
|
||
|
|
| `cooldown(duration)` | Run at most once per duration | `cooldown(30s) { Fire }` |
|
||
|
|
| `if(condition)` | Only run if condition true | `if(health > 50) { Attack }` |
|
||
|
|
| `succeed_always` | Always return success | `succeed_always { Try }` |
|
||
|
|
| `fail_always` | Always return failure | `fail_always { Test }` |
|
||
|
|
|
||
|
|
## Syntax
|
||
|
|
|
||
|
|
```bnf
|
||
|
|
<decorator> ::= <repeat-decorator>
|
||
|
|
| <invert-decorator>
|
||
|
|
| <retry-decorator>
|
||
|
|
| <timeout-decorator>
|
||
|
|
| <cooldown-decorator>
|
||
|
|
| <if-decorator>
|
||
|
|
| <force-result-decorator>
|
||
|
|
|
||
|
|
<repeat-decorator> ::= "repeat" <repeat-spec>? "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<repeat-spec> ::= "(" <number> ")"
|
||
|
|
| "(" <number> ".." <number> ")"
|
||
|
|
|
||
|
|
<invert-decorator> ::= "invert" "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<retry-decorator> ::= "retry" "(" <number> ")" "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<timeout-decorator> ::= "timeout" "(" <duration-literal> ")" "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<cooldown-decorator> ::= "cooldown" "(" <duration-literal> ")" "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<if-decorator> ::= "if" "(" <expression> ")" "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<force-result-decorator> ::= ("succeed_always" | "fail_always") "{" <behavior-node> "}"
|
||
|
|
|
||
|
|
<duration-literal> ::= <number> ("s" | "m" | "h" | "d")
|
||
|
|
```
|
||
|
|
|
||
|
|
## Repeat Decorators
|
||
|
|
|
||
|
|
### Infinite Repeat: `repeat`
|
||
|
|
|
||
|
|
Loops the child infinitely. The child is re-executed immediately after completing (success or failure).
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior InfinitePatrol {
|
||
|
|
repeat {
|
||
|
|
PatrolRoute
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Run child
|
||
|
|
2. Child completes (success or failure)
|
||
|
|
3. Immediately run child again
|
||
|
|
4. Go to step 2 (forever)
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Perpetual patrols
|
||
|
|
- Endless background processes
|
||
|
|
- Continuous monitoring
|
||
|
|
|
||
|
|
**Warning:** Infinite loops never return to parent. Ensure they're appropriate for your use case.
|
||
|
|
|
||
|
|
### Repeat N Times: `repeat N`
|
||
|
|
|
||
|
|
Repeats the child exactly N times, then returns success.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior CheckThreeTimes {
|
||
|
|
repeat(3) {
|
||
|
|
CheckDoor
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Counter = 0
|
||
|
|
2. Run child
|
||
|
|
3. Counter++
|
||
|
|
4. If counter < N, go to step 2
|
||
|
|
5. Return success
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Fixed iteration counts
|
||
|
|
- "Try three times then give up"
|
||
|
|
- Deterministic looping
|
||
|
|
|
||
|
|
### Repeat Range: `repeat min..max`
|
||
|
|
|
||
|
|
Repeats the child between min and max times. At runtime, a specific count is selected within the range.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior SearchRandomly {
|
||
|
|
repeat(2..5) {
|
||
|
|
SearchArea
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Select count C randomly from [min, max]
|
||
|
|
2. Repeat child C times (as in `repeat N`)
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Variable behavior
|
||
|
|
- Procedural variation
|
||
|
|
- Non-deterministic actions
|
||
|
|
|
||
|
|
**Validation:**
|
||
|
|
- min ≥ 0
|
||
|
|
- max ≥ min
|
||
|
|
- Both must be integers
|
||
|
|
|
||
|
|
## Invert Decorator
|
||
|
|
|
||
|
|
Inverts the child's return value: success becomes failure, failure becomes success.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior AvoidEnemies {
|
||
|
|
invert {
|
||
|
|
IsEnemyNearby // Success if NOT nearby
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Truth table:**
|
||
|
|
|
||
|
|
| Child returns | Decorator returns |
|
||
|
|
|---------------|------------------|
|
||
|
|
| Success | Failure |
|
||
|
|
| Failure | Success |
|
||
|
|
| Running | Running (unchanged) |
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Negating conditions ("if NOT X")
|
||
|
|
- Inverting success criteria
|
||
|
|
- Converting "found" to "not found"
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
behavior SafeExploration {
|
||
|
|
choose safe_actions {
|
||
|
|
// Only explore if NOT dangerous
|
||
|
|
then explore {
|
||
|
|
invert { IsDangerous }
|
||
|
|
ExploreArea
|
||
|
|
}
|
||
|
|
|
||
|
|
// Default: Stay put
|
||
|
|
Wait
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Retry Decorator
|
||
|
|
|
||
|
|
Retries the child up to N times on failure. Returns success if any attempt succeeds, failure if all N attempts fail.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior PersistentDoor {
|
||
|
|
retry(5) {
|
||
|
|
OpenLockedDoor
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Attempts = 0
|
||
|
|
2. Run child
|
||
|
|
3. If child succeeds → return success
|
||
|
|
4. If child fails:
|
||
|
|
- Attempts++
|
||
|
|
- If attempts < N, go to step 2
|
||
|
|
- Else return failure
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Unreliable actions (lockpicking, persuasion)
|
||
|
|
- Network/resource operations
|
||
|
|
- Probabilistic success
|
||
|
|
|
||
|
|
**Example with context:**
|
||
|
|
```storybook
|
||
|
|
behavior Thief_PickLock {
|
||
|
|
then attempt_entry {
|
||
|
|
// Try to pick lock (may fail)
|
||
|
|
retry(3) {
|
||
|
|
PickLock
|
||
|
|
}
|
||
|
|
|
||
|
|
// If succeeded, enter
|
||
|
|
EnterBuilding
|
||
|
|
|
||
|
|
// If failed after 3 tries, give up
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Validation:**
|
||
|
|
- N must be ≥ 1
|
||
|
|
|
||
|
|
## Timeout Decorator
|
||
|
|
|
||
|
|
Fails the child if it doesn't complete within the specified duration.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior TimeLimitedPuzzle {
|
||
|
|
timeout(30s) {
|
||
|
|
SolvePuzzle
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Start timer
|
||
|
|
2. Run child each tick
|
||
|
|
3. If child completes before timeout → return child's result
|
||
|
|
4. If timer expires → return failure (interrupt child)
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Time-limited actions
|
||
|
|
- Preventing infinite loops
|
||
|
|
- Enforcing deadlines
|
||
|
|
|
||
|
|
**Duration formats:**
|
||
|
|
- `5s` - 5 seconds
|
||
|
|
- `10m` - 10 minutes
|
||
|
|
- `2h` - 2 hours
|
||
|
|
- `3d` - 3 days
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
behavior QuickDecision {
|
||
|
|
choose timed_choice {
|
||
|
|
// Give AI 5 seconds to find optimal move
|
||
|
|
timeout(5s) {
|
||
|
|
CalculateOptimalStrategy
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fallback: Use simple heuristic
|
||
|
|
UseQuickHeuristic
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Notes:**
|
||
|
|
- Timer starts when decorator is first entered
|
||
|
|
- Timer resets if decorator exits and re-enters
|
||
|
|
- Child node should handle interruption gracefully
|
||
|
|
|
||
|
|
## Cooldown Decorator
|
||
|
|
|
||
|
|
Prevents the child from running more than once per cooldown period. Fails immediately if called within cooldown.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior SpecialAbility {
|
||
|
|
cooldown(30s) {
|
||
|
|
FireCannon
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Check last execution time
|
||
|
|
2. If (current_time - last_time) < cooldown → return failure
|
||
|
|
3. Else:
|
||
|
|
- Run child
|
||
|
|
- Record current_time as last_time
|
||
|
|
- Return child's result
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Rate-limiting abilities
|
||
|
|
- Resource cooldowns (spells, items)
|
||
|
|
- Preventing spam
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
behavior Mage_SpellCasting {
|
||
|
|
choose spells {
|
||
|
|
// Fireball: 10 second cooldown
|
||
|
|
cooldown(10s) {
|
||
|
|
CastFireball
|
||
|
|
}
|
||
|
|
|
||
|
|
// Lightning: 5 second cooldown
|
||
|
|
cooldown(5s) {
|
||
|
|
CastLightning
|
||
|
|
}
|
||
|
|
|
||
|
|
// Basic attack: No cooldown
|
||
|
|
MeleeAttack
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**State management:**
|
||
|
|
- Cooldown state persists across behavior tree ticks
|
||
|
|
- Each cooldown decorator instance has independent state
|
||
|
|
- Cooldown timers are per-entity (not global)
|
||
|
|
|
||
|
|
## If Decorator
|
||
|
|
|
||
|
|
Only runs the child if the condition is true. Fails immediately if condition is false.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior ConditionalAttack {
|
||
|
|
if(health > 50) {
|
||
|
|
AggressiveAttack
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution:**
|
||
|
|
1. Evaluate condition expression
|
||
|
|
2. If true → run child and return its result
|
||
|
|
3. If false → return failure (do not run child)
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Preconditions ("only if X")
|
||
|
|
- Resource checks ("only if have mana")
|
||
|
|
- Safety checks ("only if safe")
|
||
|
|
|
||
|
|
**Expression syntax:**
|
||
|
|
See [Expression Language](./17-expressions.md) for complete syntax.
|
||
|
|
|
||
|
|
**Examples:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior GuardedActions {
|
||
|
|
choose options {
|
||
|
|
// Only attack if have weapon and enemy close
|
||
|
|
if(has_weapon and distance < 10) {
|
||
|
|
Attack
|
||
|
|
}
|
||
|
|
|
||
|
|
// Only heal if health below 50%
|
||
|
|
if(health < (max_health * 0.5)) {
|
||
|
|
Heal
|
||
|
|
}
|
||
|
|
|
||
|
|
// Only flee if outnumbered
|
||
|
|
if(enemy_count > ally_count) {
|
||
|
|
Flee
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Comparison with bare `if` conditions:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// Using bare 'if' condition (checks every tick, no body)
|
||
|
|
then approach_and_attack {
|
||
|
|
if(enemy_nearby)
|
||
|
|
Approach
|
||
|
|
Attack
|
||
|
|
}
|
||
|
|
|
||
|
|
// Using 'if' decorator with body (precondition check, fails fast)
|
||
|
|
if(enemy_nearby) {
|
||
|
|
then do_attack {
|
||
|
|
Approach
|
||
|
|
Attack
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The `if` decorator with a body is more efficient for gating expensive subtrees.
|
||
|
|
|
||
|
|
## Force Result Decorators
|
||
|
|
|
||
|
|
### `succeed_always`
|
||
|
|
|
||
|
|
Always returns success, regardless of child's actual result.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior TryOptionalTask {
|
||
|
|
succeed_always {
|
||
|
|
AttemptBonus // Even if fails, we don't care
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Optional tasks that shouldn't block progress
|
||
|
|
- Logging/monitoring actions
|
||
|
|
- Best-effort operations
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
behavior QuestSequence {
|
||
|
|
then main_quest {
|
||
|
|
TalkToNPC
|
||
|
|
|
||
|
|
// Try to find secret, but don't fail quest if not found
|
||
|
|
succeed_always {
|
||
|
|
SearchForSecretDoor
|
||
|
|
}
|
||
|
|
|
||
|
|
ReturnToQuestGiver
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### `fail_always`
|
||
|
|
|
||
|
|
Always returns failure, regardless of child's actual result.
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior TestFailure {
|
||
|
|
fail_always {
|
||
|
|
AlwaysSucceedsAction // But we force it to fail
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Use cases:**
|
||
|
|
- Testing/debugging
|
||
|
|
- Forcing alternative paths
|
||
|
|
- Disabling branches temporarily
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
behavior UnderConstruction {
|
||
|
|
choose abilities {
|
||
|
|
// Temporarily disabled feature
|
||
|
|
fail_always {
|
||
|
|
NewExperimentalAbility
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fallback to old ability
|
||
|
|
ClassicAbility
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Combining Decorators
|
||
|
|
|
||
|
|
Decorators can nest to create complex behaviors:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior ComplexPattern {
|
||
|
|
// Repeat 3 times, each with 10 second timeout
|
||
|
|
repeat(3) {
|
||
|
|
timeout(10s) {
|
||
|
|
SolveSubproblem
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**More complex nesting:**
|
||
|
|
```storybook
|
||
|
|
behavior ResilientAction {
|
||
|
|
// If: Only if health > 30
|
||
|
|
if(health > 30) {
|
||
|
|
// Timeout: Must complete in 20 seconds
|
||
|
|
timeout(20s) {
|
||
|
|
// Retry: Try up to 5 times
|
||
|
|
retry(5) {
|
||
|
|
// Cooldown: Can only run once per minute
|
||
|
|
cooldown(1m) {
|
||
|
|
PerformComplexAction
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Execution order:** Outside → Inside
|
||
|
|
1. If checks condition
|
||
|
|
2. Timeout starts timer
|
||
|
|
3. Retry begins first attempt
|
||
|
|
4. Cooldown checks last execution time
|
||
|
|
5. Child action runs
|
||
|
|
|
||
|
|
## Duration Syntax
|
||
|
|
|
||
|
|
Timeout and cooldown decorators use duration literals:
|
||
|
|
|
||
|
|
```bnf
|
||
|
|
<duration-literal> ::= <number> <unit>
|
||
|
|
|
||
|
|
<unit> ::= "s" // seconds
|
||
|
|
| "m" // minutes
|
||
|
|
| "h" // hours
|
||
|
|
| "d" // days
|
||
|
|
|
||
|
|
<number> ::= <digit>+
|
||
|
|
```
|
||
|
|
|
||
|
|
**Examples:**
|
||
|
|
- `5s` - 5 seconds
|
||
|
|
- `30s` - 30 seconds
|
||
|
|
- `2m` - 2 minutes
|
||
|
|
- `10m` - 10 minutes
|
||
|
|
- `1h` - 1 hour
|
||
|
|
- `24h` - 24 hours
|
||
|
|
- `7d` - 7 days
|
||
|
|
|
||
|
|
**Validation:**
|
||
|
|
- Number must be positive integer
|
||
|
|
- No compound durations (use `120s` not `2m` if runtime expects seconds)
|
||
|
|
- No fractional units (`1.5m` not allowed; use `90s`)
|
||
|
|
|
||
|
|
## Comparison Table
|
||
|
|
|
||
|
|
| Decorator | Affects Success | Affects Failure | Affects Running | Stateful |
|
||
|
|
|-----------|----------------|----------------|----------------|----------|
|
||
|
|
| `repeat` | Repeat | Repeat | Wait | Yes (counter) |
|
||
|
|
| `repeat N` | Repeat | Repeat | Wait | Yes (counter) |
|
||
|
|
| `repeat min..max` | Repeat | Repeat | Wait | Yes (counter) |
|
||
|
|
| `invert` | → Failure | → Success | Unchanged | No |
|
||
|
|
| `retry N` | → Success | Retry or fail | Wait | Yes (attempts) |
|
||
|
|
| `timeout dur` | → Success | → Success | → Failure if expired | Yes (timer) |
|
||
|
|
| `cooldown dur` | → Success | → Success | → Success | Yes (last time) |
|
||
|
|
| `if(expr)` | → Success | → Success | → Success | No |
|
||
|
|
| `succeed_always` | → Success | → Success | → Success | No |
|
||
|
|
| `fail_always` | → Failure | → Failure | → Failure | No |
|
||
|
|
|
||
|
|
**Stateful decorators** maintain state across ticks. **Stateless decorators** evaluate fresh every tick.
|
||
|
|
|
||
|
|
## Validation Rules
|
||
|
|
|
||
|
|
1. **Child required**: All decorators must have exactly one child node
|
||
|
|
2. **Repeat count**: `repeat N` requires N ≥ 0
|
||
|
|
3. **Repeat range**: `repeat min..max` requires 0 ≤ min ≤ max
|
||
|
|
4. **Retry count**: `retry N` requires N ≥ 1
|
||
|
|
5. **Duration positive**: Timeout/cooldown durations must be > 0
|
||
|
|
6. **Duration format**: Must match `<number><unit>` (e.g., `10s`, `5m`)
|
||
|
|
7. **Guard expression**: Guard condition must be valid expression
|
||
|
|
8. **No empty decorators**: `repeat { }` is invalid (missing child)
|
||
|
|
|
||
|
|
## Use Cases by Category
|
||
|
|
|
||
|
|
### Timing Control
|
||
|
|
- **timeout**: Prevent infinite loops, enforce time limits
|
||
|
|
- **cooldown**: Rate-limit abilities, prevent spam
|
||
|
|
- **repeat**: Continuous processes, patrols
|
||
|
|
|
||
|
|
### Reliability
|
||
|
|
- **retry**: Handle unreliable actions, probabilistic success
|
||
|
|
- **if**: Precondition checks, resource validation
|
||
|
|
- **succeed_always**: Optional tasks, best-effort
|
||
|
|
|
||
|
|
### Logic Control
|
||
|
|
- **invert**: Negate conditions, flip results
|
||
|
|
- **fail_always**: Disable branches, testing
|
||
|
|
|
||
|
|
### Iteration
|
||
|
|
- **repeat N**: Fixed loops, deterministic behavior
|
||
|
|
- **repeat min..max**: Variable loops, procedural variation
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
### 1. Use Guards for Expensive Checks
|
||
|
|
|
||
|
|
**Avoid:**
|
||
|
|
```storybook
|
||
|
|
then expensive {
|
||
|
|
if(complex_condition)
|
||
|
|
ExpensiveOperation
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Prefer:**
|
||
|
|
```storybook
|
||
|
|
if(complex_condition) {
|
||
|
|
ExpensiveOperation // Only runs if condition passes
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Combine Timeout with Retry
|
||
|
|
|
||
|
|
**Avoid:** Infinite retry loops
|
||
|
|
|
||
|
|
**Prefer:**
|
||
|
|
```storybook
|
||
|
|
timeout(30s) {
|
||
|
|
retry(5) {
|
||
|
|
UnreliableAction
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Use Cooldowns for Rate Limiting
|
||
|
|
|
||
|
|
**Avoid:** Manual timing in actions
|
||
|
|
|
||
|
|
**Prefer:**
|
||
|
|
```storybook
|
||
|
|
cooldown(10s) {
|
||
|
|
FireCannon
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Invert for Readable Conditions
|
||
|
|
|
||
|
|
**Avoid:**
|
||
|
|
```storybook
|
||
|
|
choose options {
|
||
|
|
then branch_a {
|
||
|
|
if(not is_dangerous)
|
||
|
|
Explore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Prefer:**
|
||
|
|
```storybook
|
||
|
|
choose options {
|
||
|
|
then branch_a {
|
||
|
|
invert { IsDangerous }
|
||
|
|
Explore
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. succeed_always for Optional Tasks
|
||
|
|
|
||
|
|
**Avoid:**
|
||
|
|
```storybook
|
||
|
|
then quest {
|
||
|
|
MainTask
|
||
|
|
choose optional {
|
||
|
|
BonusTask
|
||
|
|
DoNothing // Awkward fallback
|
||
|
|
}
|
||
|
|
NextTask
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Prefer:**
|
||
|
|
```storybook
|
||
|
|
then quest {
|
||
|
|
MainTask
|
||
|
|
succeed_always { BonusTask } // Try bonus, don't fail quest
|
||
|
|
NextTask
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Cross-References
|
||
|
|
|
||
|
|
- [Behavior Trees](./11-behavior-trees.md) - Using decorators in behavior trees
|
||
|
|
- [Expression Language](./17-expressions.md) - Guard condition syntax
|
||
|
|
- [Value Types](./18-value-types.md) - Duration literals
|
||
|
|
- [Design Patterns](../advanced/20-patterns.md) - Common decorator patterns
|
||
|
|
|
||
|
|
## Related Concepts
|
||
|
|
|
||
|
|
- **Composability**: Decorators can nest for complex control flow
|
||
|
|
- **Separation of concerns**: Decorators handle control flow, children handle logic
|
||
|
|
- **State management**: Stateful decorators (repeat, retry, timeout, cooldown) persist across ticks
|
||
|
|
- **Performance**: Guards prevent unnecessary child execution
|