Tactics RPG Devlog

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:

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 Effects 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:

  1. A brief message over the actor giving the name of the action.
  2. 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