The AI tactics system for Chain Crisis lets NPCs make intelligent combat decisions based on configurable rules stored in CSV files. The goal was to create something that designers could iterate on without touching code, while still providing the control needed for complex, emergent behavior.
How It Works
The system flows through a few core components. Rules are defined in DataTables (CSV files) by designers. At BeginPlay, the TacticComponent loads and caches these rules, applying difficulty scaling to cooldowns. Then BTService_EvaluateTactics runs every tick—about 0.5 seconds, with some randomization—evaluating which rules are valid and selecting the highest priority one. It sets blackboard values, and the behavior tree picks the right movement branch to execute the action.
┌─────────────────────┐
│ DataTable (CSV) │ ← Rules defined by designers
└──────────┬──────────┘
│ Loaded at BeginPlay
↓
┌─────────────────────┐
│ TacticComponent │ ← Caches rules, tracks cooldowns
└──────────┬──────────┘
│ Used by
↓
┌─────────────────────┐
│ BTService_Evaluate │ ← Evaluates rules every tick
│ Tactics │ Sets blackboard values
└──────────┬──────────┘
│ Triggers
↓
┌─────────────────────┐
│ Behavior Tree │ ← Movement branches execute actions
│ (BT_Tactics) │ Each branch calls MarkTacticComplete
└─────────────────────┘The architecture is intentionally simple: data flows one way, from CSV to component to service to behavior tree. This makes it easier to reason about and debug.
Difficulty Scaling
Difficulty needs to actually matter, not just be a damage multiplier. So the system scales AI behavior based on difficulty level. On Easy, cooldowns are 50% longer and reactions are 25% slower. On Extreme, cooldowns are 50% shorter and reactions are 40% faster. This creates meaningfully different encounters at each difficulty.
The scaling happens at initialization for cooldowns, and affects the evaluation tick rate for reactions. This way the behavior stays consistent within a difficulty level, but the differences between levels are noticeable.
Cooldown Tracking
Each tactic rule has its own cooldown, tracked by the rule's unique name—not by action type. This was important because multiple rules using the same action need independent cooldowns. For example, you might have a close-range version of an ability with a 4-second cooldown, and a long-range version with a 3-second cooldown. They're the same action type, but different rules, so they track separately.
This design enables more nuanced behavior. An AI can have different cooldown timings for the same ability used in different contexts, which creates more varied and interesting combat.
State Machine
The system uses a simple state machine to manage the action lifecycle. It starts at Idle, moves to Pending when a rule is selected, then to Completing when execution finishes. The service handles cleanup and returns to Idle.
The design keeps this simple. The service sets Pending, the behavior tree sets Completing, and the service handles everything else. Tasks are read-only—they don't modify state directly. This reduces coupling and makes behavior more predictable.
Execution Flow
Here's what happens on each evaluation tick:
- The service checks all cached rules, filtering by difficulty gating, cooldowns, and ability availability
- It evaluates conditions—both self conditions (health, ammo, tags) and target conditions
- It selects the highest priority valid rule (higher priority can interrupt lower priority)
- It sets blackboard values: rule name, action, target, movement type, desired range
- The behavior tree detects the
Pendingstate and selects the appropriate movement branch - The movement branch executes—
Advance,Retreat,Cover,Flank,Push,Backpedal,Pursue,Withdraw, orHold - When done, the service detects completion, records execution time, releases combat tokens, and clears state
The whole thing runs smoothly because each component has a clear responsibility.
Why This Design
A few principles guided the architecture:
Data-driven over hardcoded. Rules live in CSV files, so designers can iterate without code changes. This is faster for iteration and keeps the system flexible.
Unambiguous tracking. Rules are tracked by their unique DataTable row name (stored as FName), not by action type. This prevents confusion when multiple rules share the same action.
Read-only tasks. Tasks don't modify state directly. This was a lesson learned from an earlier version where tasks modified state, which created tight coupling and made behavior harder to predict.
Difficulty-aware. The system scales behavior based on difficulty level, creating meaningful progression without just tweaking numbers.
Performance-conscious. The evaluation frequency is balanced—responsive enough to feel good, but not so frequent that it hogs CPU.
Debugging
Built in comprehensive visual debugging. When enabled, you can see state information floating above AI characters, target visualization with lines and circles, desired range indicators, line of sight checks, and execution status. This makes it much easier to understand what's happening during development and testing.
The system also includes migration notes documenting how the design evolved. We moved away from an architecture where tasks modified state directly, which was causing coupling issues. The current design is cleaner and more maintainable.
This is the kind of system that scales through interaction rather than content volume. The AI Tactics system shows that complex, emergent behavior can come from well-designed, data-driven systems. You don't need to hardcode every scenario. You need good rules and good architecture.