- related to Roguelike, Game Development
- under Blog, Devlog
Terminology
Actions
Actions are inputs given by Actors.
Common fields includes
- actor
- An Actor entity that performs this Action
- cost
- the time cost of this Action
- target
- A location or an entity that is the victim of this Action
The Action system dispatch the Action into one or more Events.
Actor
Actors includes the player, mobs, and other "non-creatures" like traps with timers.
Each Actor has a timestamp. When picking the next Actor, the turn system pick the Actor with the earliest timestamp.
When an Action is taken, the time cost of this Action is added to the timestamp of the Actor.
Events
Events are things that happen in the world.
Each event has a source and a destination, and both of them are entities.
They are not final results however: When a change is guaranteed to happen, and the numbers in the change is fixed, an outcome is generated.
Outcomes
Outcomes are the results of Events. They are guaranteed observable modifications to the world, and they are for "publishing" only.
They are thus should be potentially known by the UI: each of them should be able to produce
- a message log, describing what happens with details
- an animation, demonstrating quickly what happens
- a traversable log, as each outcome is guaranteed to be applicable and revertible
Phases
Pick an Actor
The turn system pick an Actor with the earliest timestamp.
- If it's the player, the game waits for the player's input.
- Otherwise, the mob's AI gives the next action to do.
Dispatch the Action
Based on the given action, the system generates Events that specifies a source entity, a destination entity and related effects.
Handle Events
Until there is no event left, the system iterates over all events, applies each of them, and possibly generates new events.
When an event is observable (i.e. it's UI-wise meaningful), it's not removed immediately, but changed to an Outcome.
Eventually, the system will enter a peaceful state where there is no event but outcomes.
Publish Outcomes
From the above process, we collects observable Events as Outcomes. The system can then publish them to the UI.
Example: Bump with firebrand sword
Pick Actor
Player as an Actor has the earliest timestamp, so the turn system picked the player to give the next Action.
The player inputs with "Bump" targeting a tile next to the player character, where there is a goblin on that tile.
Bump Action(actor=player, target=(0, 1), cost=100)
Dispatch Action
First, since it's an action, we generate a event to update the timestamp:
Event(src=player, dst=player) TimeEffect(cost=100)
Also, the actual Event for the movement:
Event(src=player, dst=player) MoveEffect(delta=(0, 1))
Handle Events
iteration 1
The event with the time effect is handled. Since the player is not speed up or slow down, it is applied without change:
Event(src=player, dst=player) TimeUpdate(delta=100)
The event with the movement effect is handled. The system figure out that there is a hostile mob (goblin) in the tile, so it is translated into an attack Event:
Event(src=player, dst=player) AttackEffect(target=(0, 1))
iteration 2
Time update is something meaningful, thus it's saved as an Outcome:
Outcome(dst=player) TimeUpdate(delta=100)
There is only one mob in the given tile, so the destination is determined to be the goblin.
Then, the system figures out that the player is holding a firebrand sword. It generates these raw damages:
Event(src=player, dst=goblin) MeleeDamage(damage=10) FireDamage(damage=6)
iteration 3
Now the system takes goblin's defense into account. The results are
Event(src=player, dst=goblin) HpModify(delta=-7, type=melee) Event(src=player, dst=goblin) HpModify(delta=-5, type=fire)
iteration 4
The system applies these HpModify s. Clearly the goblin is dead:
Event(src=player, dst=goblin) Kill
Also, HpModify is observable events, so they are saved as Outcomes:
Outcome(src=player, dst=goblin) HpModify(delta=-7, type=melee) Outcome(src=player, dst=goblin) HpModify(delta=-5, type=fire)
iteration 5
Kill in an RPG world means gaining experience too:
Event(src=goblin, dst=player) GainExperience(10)
And it's an outcome of course:
Outcome(src=player, dst=goblin) Kill
iteration 6
Gaining experience is also observable, so it's becoming an Outcome:
Outcome(src=goblin, dst=player) GainExperience(10)
Publish Outcomes
Now we have these outcomes:
Outcome(dst=player) TimeUpdate(delta=100) Outcome(src=player, dst=goblin) HpModify(delta=-7, type=melee) Outcome(src=player, dst=goblin) HpModify(delta=-5, type=fire) Outcome(src=player, dst=goblin) Kill Outcome(src=goblin, dst=player) GainExperience(10)
The system finally publish these outcomes to the UI. As messages, for example, it can be
The player did 7 melee damage to the goblin. The player did 5 fire damage to the goblin. The goblin was killed by the player. The player gained 10 exp.
As a bonus, if at this state the only Outcome is TimeUpdate, the system shall be able to revert it and cancel the Action. For example, if the player mistypes and hits a wall.