A recent discussion on how the
Convert ability should work brought up the question how we want to handle interaction between different game entities. In our previous drafts, every interaction had its own ability. This aproach works, but inevitably creates redundancy. We introduced a new mechanic in form of
Effect objects to address these issues and hopefully make the engine even more extensible along the way.
Other interesting articles in the modding API series:
- Units, Buildings & more
- Inventory System
- Too many villagers!
- Restocking farms
Many abilities, many overlaps
The main problem with our interaction abilities is the lack of consistency in their mechanics. Some -- like
Repair -- are one-sided as the outcome is solely decided by the unit that executes the ability, while others such as
Attack are two-sided with the final attack value depending on both the attacker and the defender. Also, there are specialized abilities for the purpose of activating a
ResourceSpot like the
Hunt ability. And of course there's
Convert with weird and ridiculous rules (thanks go out to Jineapple who figured all of this out).
One major drawback of having different behaviors for every interaction is that they are not compatible with each other. It is not possible to have an
Attack that does damage and has a chance to convert for example. The idea behind the reorganization of abilities into an effect-based system is to eliminate these incompatabilities and to make combinations of different effects possible.
Effect and Resistance
The new system draws heavy inspiration from the old attack definition described in blogpost No. 5. It is recommended that you read the whole article if you haven't already.
Every interaction is now two-sided and modeled through a pair of
Effects are defined by the applicant of the effect, while the
Resistance is always defined by the unit the effect is applied on. For convenience sake, we will call the two sides "effector" and "resistor" from now on.
The mechanics that were previously tied to abilities are now defined as generic effects and resistances, albeit with different names. For example, the
ArmorDefense objects previously used for the
Attack ability are now covered by
FlatAttributeChange (on the left) and
FlatAttributeResistance (on the right). Despite the bulky object names, attacking still works the same way as before. Newly added are
ChanceEffects, which can be used for conversion, and
MakeHarvestable that makes
ResourceSpots accessible to villagers.
One important mechanic that was carried over from the attack system is that the application of an
Effect by an effector always requires the corresponding
Resistance on the resistor's side. For example, if a resistor has no
ConversionResistance defined, it cannot be converted with the
Conversion effect. This is slightly counterintuitive, but makes giving units immunities much easier. If you want a conversion with zero resistance, this can instead be done by assigning a
ConversionResistance object with
chance_resist set to 0 to the resistor.
Resistance objects can usually be used both ways. Either for the benefit or for the disadvantage of the targeted game entity. For example, setting a positive value for
FlatAttributeChange will damage a unit, while a negative value essentially be a heal. To prevent a heal from accidently damaging the resistor, modders can define minimum and maximum value limits for an
Effect. This is entirely optional, so you can also choose not to and let everything go haywire.
Calculation examples can be found in the Addenndum section at the bottom of the article.
Discrete vs. Continuous
The current effects can come in two forms, discrete and continuous.
DiscreteEffects are applied immediatly at a specific point in time.
ContinuousEffects on the other hand happen over time at a defined rate (e.g. reduction of attribute points per second). The differentiation is necessary because an ability using
DiscreteEffect needs to define the application interval which is not necessary for
ContinuousEffect. Examples for the application of a
ContinuousEffect in Age of Empires include healing and repairing.
The new abilities
With the introduction of effects, the interaction abilities are subject to a lot of changes, too. As we can cover most of them with an
Repair ability do not need to be part of the API anymore, indicated by their white color. Instead, they are from now on handled as derivatives of
ApplyContinuousEffect shown in the centre of the diagram. For example, implementing the old
Heal ability would be realized by inheriting
ApplyContinuousEffect and defining one or more continuous
FlatAttributeChange effects. A dedicated
Heal ability is unnecessary as the needed definitions are stored in the effect. The only specific ability remaining is
DiscreteAttack because the API does not have a concept of diplomatic stances yet which is required for recognizing friendly fire (although this could change in a later API draft). Resistances are stored in the
Resistance ability (shown in the upper right corner).
For every general application ability there also are two more specialized API abilities available:
RangedXXXEffect one can define the minimum distance to a unit for the application of effects.
AreaOfXXXEffect applies the effects in an area around the effector. This was previously only modeled for attacking, but now it is available for all effects. This means that you can make super monks that convert everything around them. Neat! Because
effects is a set member, all abilities are able to bundle and apply multiple effects at once.
The greatest advantage of this system is that it has eliminated the incompatibilities mentioned in the beginning. Delegating the behavior to individual effects allows us to derive abilities that combine them freely without being restricted to a specific type of effect. Theoretically, an ability can even apply both continuous and discrete effects by inheriting from
More to come..?
The next logical step to improve the effect system would obviously be the definition of even more effect and resistance types. Right now, the API only features 4 concrete effects and resistances and there is room for more. Extensions could be damage-over-time effects like Poison or Fire Damage as well as more creative effects like a Life Steal. Another addition would be allowing effects to attach modifiers to other units that give temporary buffs or debuffs, e.g. a Disease.
There is also the question remaining whether
Build are supposed to be effects or if they should be left as separate abilities. Both of them are candidates as they define a form of interaction, but modeling them as effects might require further depature from the way they were implemented in AoE2. Therefore, we will postpone the decision to a later date.
Hint: You can suggest your own improvement ideas on Reddit. It's not forbidden to question the devs.
Do you still have questions? Then let us know and discuss them with us and the community by visiting our subreddit /r/openage!
As always, if you want to reach us directly in the dev chatroom:
Addendum: Example Calculations
Defining and calculating the result of an effect-resistance pairing is very easy and straight forward. Look at the example below for a simple interaction effect involving
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
The effector's attack does 4 health points of damage and the resistor blocks 1 damage, so the overall damage done is 3. Keep in mind that
Resistance have to match up for this to work. For
FlatAttributeChange, the match is defined by the
type member of
FlatAttributeResistance and the
type member of
AttributeAmount. An example where the pair doesn't match up can be seen below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
AttributeAmount in the effect does not match the
AttributeAmount in the resistance. In this case the resistor is immune to the
MeleeAttack effect, but usually effector's apply more than one effect and resistor's have more than one resistance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
This time, multiple effects come into play. The effect
MeleeAttack attack has no match on the resistor's side, so it is ignored.
PierceAttack does match up with
PierceResistance, so the damage can be calculated. With a damage value of 2 and a block value of 5, the overall change to the resistor's health would be
-3, which is equivalent a heal of 3. Because the effector has initialized
min_change_value with 0, the change is rounded up to that number. As a result 0 pierce damage is done.
Convert also has a match (by type) on the resistor's side in form of
ConversionResistance. The chance is calculated by substracting
chance_success, resulting in an overall chance of
0.4 (40%) for the effector to be successful.