Action
Overview
In short, action is the object that statically describes the logic involved in a particular action in the game. For example, and AttackAction
would kind of describe how to do an attack.
Note: Action instances are actually not completely static. One may add a
direction
and effects likeattack
,push
,dig
andmove
onto it.
When an entity decides on an action, it is said to have computed
it. When this happens, it sets its entity.nextAction
to this computed action. For enemies, this functionality is provided by the Sequential
decorator, while for players, it's the PlayerControl
decorator.
At the time of computation, the fresh action does not include a direction for enemies. In this case the direction is computed separately by the action algorithm (e.g.
GeneralAlgo
)
Action Execution
The action execution process is pretty convoluted. Let's break it down into components to clarify how it works.
Acting
Prerequisites for becoming an Acting
is a decorator for entities. Entities decorated with Acting
have the function executeAction()
(which is just a shorthand for entity.decorators.Acting:activation()
) actually doing something interesting. If the instance class has not been decorated with Acting
, executeAction
would just set Acting.didAction
to true
.
Acting.something
in this and subsequent cases will refer to the something
field on an entity class decorated with Acting
.
The Acting
decorator can be applied to both player and non-player entities. Once done, though, you'll still have to provide the algorithm for action, that is, put an Algo
handler into the new action
chain received from the Acting
decorator. Technically, you may add more than one algorithm.
Structure
Before starting on this, some new terminology has to be introduced:
- The event at the highest level, set as
Acting.enclosingEvent
, which is generated byActing.executeAction()
is the enclosingEvent. - The succeeded event nested inside it that is generated by the action algorithm, will be called the algoEvent.
- The event produced by the functions
Attacking.executeAttack()
,Moving.executeMove()
and so on will be called resultEvent. - The events that come and go without being saved anywhere are internalEvent's.
Each Acting NonPlayerReal
(Acting
is a decorator) has a set of fields that reflect the action execution state:
Acting.didAction
is settrue
once the action has been completely executed. Thegame loop
, naturally, ignores them, so that the action is not repeated for many times over (remember, theActing
entities may make others act).Acting.doingAction
is settrue
once theActing.executeAction()
has been called, andfalse
once exited.
Now we'll examine the final event structure once it comes out of Acting.executeAction()
(assume GeneralAlgo
). It does not 'come out' as such, the function always returns nothing. The final event is saved as Acting.enclosingEvent
. The standart fields of any event, which are event.actor
, event.action
and event.propagate
, exist on each of the event structures examined, so they will be consequently omitted.
This event has a special structure, and consequently will be called EnclosingEvent
:
EnclosingEvent.checkSuccess
- (boolean) same asevent.propagate
, except not inherited.EnclosingEvent.success
- (boolean) comes paired with the next field.EnclosingEvent.algoEvent
- The one event that actually occured, with the action and the direction:algoEvent.success
- A nested
resultEvent
with a wild diversity of more fields depending on the action type. E.g. for anAttackAction
, these would be:resultEvent.attack
- the attack object appliedresultEvent.push
- the push object appliedresultEvent.status
- the status object appliedresultEvent.targets
- a list of theTarget
objects, containing the reals actually hit. This list is formed by the weapon's spec or by taking the cell the actor is facing and getting the real out of it.resultEvent.attackEvents
- list of events generated as a result of reals being attackedresultEvent.pushEvents
- similarly, pushedresultEvent.statusEvents
- similarly, statusedresultEvent.success
- whether thecheck
orget
(getAttack
in this case) chain has been passed entirely through
You can find the one direction that succeeded (assuming GeneralAlgo
) in enclosingEvent.action.direction
.
The algorithms
Most common algorithms, which act by doing something in a direction, are the GeneralAlgo
for enemies and SimpleAlgo
for the player and for non-enemy entities.
The difference between these two is that the GeneralAlgo
tests out a couple of desirable actions, which it would get from entity.sequence.getMovs()
function (this function is set for each step of the sequence individually, see Sequence), gets the most desirable one out of them, and executes that single one, while the SimpleAlgo
just does the selected action in the only direction provided (which came from user input).
As a result, these algorithms support just one action of one type at a time. That is, with the GeneralAlgo
it is impossible to program an enemy that e.g. would attack to the left, while spitting out a projectile to the right (it is possible, but hacky), which is also true for the SimpleAlgo
.
Action chains of both enemies and players typically have a verification stage before deciding on which action to start. These are set on the get
(or check
) chain from the corresponding decorator. These chains are incorporated in the chainTemplate
directly on the entity class. For example, for an attack, the decorator would be Attacking
and the chain would be named getAttack
. So you would do:
MyEntity.chainTemplate:addHandler('getAttack', myHandler)
There will be plenty of predefined handlers, but these are in fact just normal chain handlers that work with events. You can write ones yourself easily.
The repercussions of this design is that the actions resulting in no avail, e.g. attacking empty space, must be foreseen and prevented manually for both player and non-player entities.
Both of these algorithms also save the action that was relevant and set success to true. See Structure.
An action 'succeeding'
Actions may be multi-step. That is, they may have multiple components to them. For example, there is the AttackMoveAction
which indicates first attacking, then moving. So, attacking and moving are both action components in this case.
An action component is considered to have succeeded, if its resultEvent
went through every single handler in the action component check (or get) chain. For Attack
, this would mean it went through every single handler of the getAttack
chain. This is why for checking purposes one should add handlers that would stop the event to the check (or get) chains.
In the case of an action component succeeding, the action as a whole would return, then the resultEvent
would be saved on algoEvent
and algoEvent.succeed
would be set to true
. Otherwise, algoEvent.succeed
would be false
, while resultEvent
would be nil
.