Designing an Action System
It's time to move on to actions! For now, I'll concern my self with a basic attack. When the player move's "into" an adjacent enemy, it will trigger an attack that does random damage based on the unit's strength
.
Designing an Action Data Type
I want to make actions as generic as possible:
An action applies a list of effects to a target at a range.
Effects can be damage, healing, status effects, etc.
A range denotes what the effects are applied to. Some examples:
- A single unit within 3 tiles of the actor.
- All units within 2 tiles of the actor.
- A tile adjacent to the actor.
Not all effects will be defined for all ranges. That's ok.
In other words, a range denotes a set of targets.
For now, this will be the action data model:
record Action = {
name: String,
range: Range,
effects: List EffectTemplate
}
sum Range
= SingleUnit { min_range: Int, max_range: Int }
sum EffectTemplate
= Damage { min: Int, max: Int, bonus: Int }
sum Effect
= Damage { target: UnitID, min: Int, max: Int }
Notice that we need both EffectTemplate
and Effect
. An EffectTemplate
is an effect without a target. Once a target is chosen, we can get a list of Effect
s from an Action
.
So basic attack will be:
basic_attack = Action {
name: "Attack",
range: SingleUnit { min_range: 1, max_range: 1 },
effects: List(
Damage { min: 0, max: 2, bonus: 1 // strength }
),
}
Some possible future actions:
shoot_bow = Action {
name: "Shoot",
range: SingleUnit { min_range: 2, max_range: 5 },
effects: List(
Damage { min: 1, max: 4, bonus: 0 },
),
}
fireball = Action {
name: "Fireball",
range: UnitsInCircle { min_range: 2, max_range: 5, radius: 3 },
effects: List(
Damage { min: 5, max: 10, bonus: 0, type: Fire },
),
}
light = Action {
name: "Illuminate",
range: SingleTile { min_range: 0, max_range: 0 },
effects: List(
CreateLight { radius: 3, color: White }
),
}
The only downside I can see is that I might want an action that effects two targets differently. Imagine a vampires "Blood Suck", which should damage an adjacent unit and heal the vampire. I could add a self_override
field to effect templates that says this effect should target the actor.
I don't think there will be a situation where I'll want to do something like "damage this enemy but poison that enemy."
Rolling Dice
Whenever there's a random range like "1 to 4", I'll roll the range twice and average. So 1d4
will be implemented as 2d4 / 2
. This should tame the randomness a bit.
UI and Graphics
When an an action is execued, this is what we should see:
- A brief message over the actor giving the name of the action.
- For each effect, an effect-specific animation.
For now, the animation for "Damage" will just be a number showing damage over the target.
Data Model
In addition to the action data model I just described, I'll need:
sum Effect
= Damage { min: Int, max: Int, target: UnitID }
| Kill { target: UnitID }
sum Animation
= PanelMessage { coord: Coord, text: String, color: Color } // For action name
| Message { coord: Coord, text: String, color: Color } // For numbers
record Unit =
...
strength: Int // added to basic attack rolls
record NpcBehavior =
...
select_action: (Unit, Game) => Option Action
sum GameState =
...
| SelectingAction
| NpcSelectingAction
| ExecutingEffects { effects: List Effect }
| ShowingAnimations { animations: List Animation, effects_backlog: List Effect }
Criteria
- The player executes a basic attack when:
- It "moves" into an NPC during it's move phase OR
- It "moves" into an NPC during it's action phase.
- The goon will execute a basic attack when it's adjacent to the player during its action phase.
- Actions are displayed as specified.
- Damage works as one would expect.