Buffs (Legacy)
A buff
is a property of an entity whose value is derived from a set of contributed values.
Buffs are normally used to represent temporary effects on an entity, such as a speed boost or a damage reduction.
Buffs have been superceded by Collectors. Buffs will continue to be supported but will no longer be updated. You should now be using Collectors instead in all new code as they are more flexible.
A buff involves a number of entities - one receiver and multiple senders.
Senders each contribute their value to the buff using a statement like Boost(1)
.
Whenever a sender despawns, their contribution is removed.
Upon reading the buff's value, the contributed values are integrated into a single value according to a custom function.
This result is then cached and only recalculated when the contributed values change.
Additionally, it is possible to await onto the buff to detect when it changes.
pub buff owner.Boost ~ |boosts| boosts.Sum
pub fn owner.Example() {
Transmission {
P { "Press X to boost!" }
}
on ButtonDown(BtnX) {
Subspawn { // create a new entity to represent the temporary effect
Boost(1) // this entity contribute a value to the buff
once Tick(5s) { Expire } // the entity and its contribution expire after 5 seconds
}
}
with Boost {
Transmission {
P { "Your current boost: " + Boost }
}
}
}
Declaration
Every buff must have an object parameter. The object parameter designates the entity that will be the receiver of the buff.
The buff's name must begin with an uppercase letter.
buff owner.Boost
Buffs can be declared as public using the pub
keyword,
which allows them to be accessed from all files in the program.
Otherwise they are private and can only be accessed from within the same file.
If two buffs have the same name but are in different files,
they are not the same buff and will not interfere with each other.
pub buff owner.Boost
Buffs may optionally be declared with a meld function, which is used to integrate the contributed values into a single value. The meld function is a Callback that receives an array of contributed values and must return a single value.
pub buff owner.Boost ~ |boosts| boosts.Sum
Contributing a value
A sender contributes a value to a buff calling it as a function.
Boost(1) // contribute the value 1 to the buff
In the above example, the sender and receiver entities are inferred implicitly from context.
This is the conventional way to contribute a value to a buff.
However, if this
is the sender and owner
is the receiver,
we could instead specify these explicitly as follows:
this.Boost(1, owner)
Normally, a single entity can only contribute a single value to the buff. Assigning a new contribution to the buff replaces the old contribution. It is possible to contribute multiple values to the same buff using an ID.
Boost<fred>(1)
Boost<fred>(2) // replace the previous contribution
Boost<george>(3) // add a new contribution
It is possible to explicitly delete a contribution using the delete
keyword.
delete Boost
When the sender expires, its contribution is removed from the buff automatically.
Reading the value
The buff's value can be read the same way as any other property:
let value = Boost // implicit object (recommended)
let value = this.Boost // or explicit object
When the buff is read, the contributed values are melded into a single value according to its meld function, included in the buff declaration. The meld function receives an array of contributed values and must return a single value. For example, this is a meld function that returns the sum of all of its contributed values:
pub buff owner.Boost ~ |boosts| boosts.Sum
A statement block may also be used instead of an expression:
pub buff owner.Boost ~ |boosts| {
return boosts.Sum
}
It is possible to omit the meld function, in which case the maximum value will be returned instead. For example:
pub buff owner.Boost
The result of the meld function is cached, and the buff will keep returning the same value without recalculating it until the contributed values change.
Awaiting change
It is possible to await the buff to detect when it changes, for example:
await Boost
This will suspend execution until the contributions to the buff change.
Like all await
functions, it is possible to use with
, on
or once
to handle the change:
with Boost {
Transmission {
P { "Your current boost: " + Boost }
}
}
Note that the buff does not return any value when awaited.
The buff must be read separately in order to obtain its value.
This can be used to avoid unnecessary recalculations.
For example, if a buff changes frequently
but its value is only needed to update a Transmission
,
then it may be a good idea to await for the Paint
event before updating the Transmission.
Changing the user interface more frequently than once per tick would never be seen by the player
and so would be a waste of computation.
with Boost {
await Paint
Transmission {
P { "Your current boost: " + Boost }
}
}
Migrating to Collectors
Buffs have been superceded by a more flexible system called Collectors. Collectors can do everything that Buffs could do, and more. Collectors are now the new way to do things, and you should use them in all new code.
Differences between Collectors and Buffs
Defaults
Collectors default to collecting all values into an Array, which is more general and intuitive behavior.
pub collect owner.AllMyBuildings
Buffs choose the maximum value by default, and making them collect into an Array requires what looks like a hack:
pub buff owner.AllMyBuildings ~ |values| values
Automatic IDs
Collectors use automatic IDs, which is more in line with how the rest of Easel works so they align better with people's expectations.
Buffs require manual IDs if want to have more than one on the same Entity. This makes it easy to interfere with other Buffs if you are not careful.
For example, the following two lines would overwrite each other for Buffs, but would be independent values for Collectors:
SpeedModifier(0.9)
SpeedModifier(1.7)
The lines do not need to be next to each other, in the same function, or even in the same file, to interfere with each other like this. This is a very subtle problem that can cross your entire codebase and can cause hard-to-find bugs. This is why Easel uses Automatic IDs not just for Collectors, but for everything else as well.
Object parameter
Collectors use a new give
statement that lets you specify the target entity using the object parameter,
which matches how they are declared.
give that.SpeedModifier(1.5)
Buffs take their target as a second parameter, which does not match how they are declared and so is less intuitive:
SpeedModifier(1.5, that)
Delete syntax
Collectors are deleted using their ID, allowing a more direct correlation between creation and deletion:
give<speedBoost> that.SpeedModifier(1.5)
delete give<speedBoost>
Buffs are deleted using the target entity, and sometimes an ID but not always:
SpeedModifier(1.5)
delete SpeedModifier
Migrating from Buffs to Collectors
Collectors can do everything Buffs can do, and so every Buff can be replaced with a Collector.
Here is a step-by-step guide for converting a buff
into a collect
:
Replace your declaration
If your Buff has either a custom merge function or a default expression, congratulations, it is as simple as renaming from buff
to collect
:
pub buff unit.SpeedBoost ~ |values| values.Sum
In this case, simply replace buff
with collect
:
pub collect unit.SpeedBoost ~ |values| values.Sum
If your buff does not have a merge function, you need to give it one because the default behavior has changed for Collectors:
pub buff unit.RelayTarget
Change this to:
pub collect unit.RelayTarget ~ Max
The default behavior of buffs is to take the maximum value, and so this will make a collector that behaves exactly the same as your original buff.
Give IDs
Look at each place where you give a value to the Buff. Are there any cases where you call a buff more than once in the same file? For buffs this would overwrite the previous contribution rather than adding a new one, and so we need to keep that behavior for your code to continue working.
SpeedModifier(0.5)
// ...
once Tick(5s) {
SpeedModifier(0.7) // Replaces the previous SpeedModifier
}
Change it to this:
give<speedBoost> SpeedModifier(0.5)
// ...
once Tick(5s) {
give<speedBoost> SpeedModifier(0.7) // Replaces previous <speedBoost>
}
Deletion IDs
Do you delete
the Buff anywhere?
You now need to use an ID to do that.
SpeedModifier(0.5)
// ...
once Tick(5s) {
delete SpeedModifier
}
Replace this with the following:
give<speedBoost> SpeedModifier(0.5)
once Tick(5s) {
delete give<speedBoost> // Deletes the previous <speedBoost>
}
Targets
Do you ever use the target entity as a parameter?
SpeedModifier(0.5, that)
Replace this with the following:
give that.SpeedModifier(0.5)
Final steps
Repeat this for every buff in your code.
Congratulations, you've now successfully migrated from Buffs to Collectors!