TempModifiers
An in-depth explanation of the fundamentals of temperature automation
A player's temperatures are affected using TempModifiers. Individual instances of these modifiers can be applied to/removed from a player and affect their temperatures in various ways.
Creation
Each TempModifier is defined in its own class, extending the abstract TempModifier
class. For a standard implementation, there is only one method to be overridden:
calculate()
calculate()
This is the method where the calculation of the TempModifier occurs. The output is a function that takes a double representing the entity's current temperature, and outputs the new temperature.
entity
- The entity that this TempModifier instance is attached totrait
- The trait that this TempModifier is being applied for. This is useful if a TempModifier is applicable to multiple traits.
It's preferable to keep as much logic outside the returned function as possible if it doesn't need to be performed every tick.
TempModifiers can be configured to run calculations on a slower interval. See tickRate() for more information.
For example:
By creating a final variable and passing it into the function, player.blockPosition().getY()
is only called when the TempModifier is recalculated (which might be every 5 ticks, 10, 20, etc.). Otherwise, the already-calculated value of entityY
is used, requiring no more computation. This can be very useful for complex TempModifiers that would be extremely expensive to run every tick.
Example Class:
The Logic of Functions
As has been stated, TempModifiers produce a Function<Double, Double>
when calculate()
is called, then store it.
Functions are used instead of static numbers because TempModifiers can be stacked, and the output of a TempModifier relies on the output of the one before it.
Functions allow for the interactions between them to be dynamic, better support TempModifiers with different tick rates, and can adapt when TempModifiers are added or removed from the entity.
In-Line Builders
When creating a new instance of a TempModifier, there a few functions that can modify their behavior on a per-instance basis.
expires()
expires()
This gives the TempModifier instance a "lifespan", and it will be automatically removed from the entity after the specified number of ticks. If needed, this duration can be changed after the modifier has been created calling TempModifier#expires(int ticks)
on it again.
This expire time can also be read by using TempModifier#getExpireTicks()
.
tickRate()
tickRate()
This changes the functionality of the TempModifier instance to only call calculate()
at the specified interval (every X ticks). On every other tick that does not land on the interval, the modifier will use the last function it generated.
This can save massively on performance if your TempModifier does not have to be updated every single tick (TempModifiers in vanilla Cold Sweat rarely do).
Note: calculate()
is always called when a TempModifier is queried for the first time, even if the current tick does not land on the chosen interval.
These methods can be chained together along with a TempModifier instance. For example:
Here, we are creating a new MyTempModifier
that will expire automatically after 10 ticks and calls calculate()
to update its function every 5 ticks.
Usage
TempModifiers can be manipulated, added, removed, etc. with the use of helper methods. These methods directly interface with the entity's capability, as well as send update packets when necessary.
addModifier()
addModifier()
entity
- The Player entity that thisTempModifier
instance will be attached tomodifier
- An instance of theTempModifier
that should be attached to the entitytrait
- The type of temperature on the entity that the modifier should be affectingduplicates
- Controls how duplicates are handled when adding a TempModifiers.
With the possibility of having multiple instances of the same TempModifier on an entity, Placement.Duplicates
is a way to flag how duplicates will be handled. There are 3 possible duplicate policies:
ALLOW
: No check for duplicatesBY_CLASS
: Passes only if a TempModifier of the same class existsEXACT
: Passes only if A TempModifier of the same class and NBT data exists
Placements
For more granular control over the ordering of TempModifier
s on an entity (since the order in which TempModifier
s are applied matters greatly), there is a version of this method that takes in a Placement
object that indicates where in the stack the modifier should be placed:
A placement compares each TempModifier
on the target entity with a Predicate<TempModifier>
, then inserts the TempModifier
at that location when a match is found. maxCount
indicates the maximum number of additions or replacements allowed for this operation.
Placements have 4 modes:
BEFORE
: Indicates that the new TempModifier should be inserted before the first TempModifier instance that matches the predicate.AFTER
: Places the TempModifer after the first match.REPLACE
: Replaces the first matched TempModifier with the new one. If a match isn't found, this operation fails and the new TempModifier is not added.REPLACE_OR_ADD
: Similar to replace, but the TempModifier will be added to the end of the stack if a match isn't found.
Placements also have 2 orders:
FIRST
: Iterates through the TempModifier stack in sequential order, starting from index 0.LAST
: Starts from the last index, and iterates through the TempModifier stack in reverse order.
Example:
Note how Placement.of()
is used to create a new Placement instance.
This code adds a new UndergroundTempModifier instance directly after the first instance of a BiomeTempModifier in the stack.
mod -> mod instanceof BiomeTempModifier
is the predicate that compares every TempModifier on the entity and selects a spot to insert the new TempModifier.
For convenience, Placement.AFTER_LAST
and BEFORE_FIRST
are static Placement instances that simply place the TempModifier at the beginning or end of the stack.
removeModifiers()
removeModifiers()
Removing a TempModifier from the stack also uses a Predicate<TempModifier>
to evaluate which TempModifiers should be targeted. A Placement.Order
is also used to determine the order in which the elements of the TempModifier stack will be compared.
Example:
This example removes the first 4 TempModifiers that have a tick rate of less than 10 from the entity's BASE temperature trait.
Other Operations
Aside from the basic adding and removing of modifiers, there are some other helpful functions for TempModifier-related logic:
getModifier()
getModifier()
Returns the first found instance of
getModifiers()
getModifiers()
Returns a list of TempModifiers of the specified trait
that are attached to the given entity. The returned list should NOT be directly used to add TempModifiers because it would circumvent the synchronization present in addModifier()
, which could have unintended side-effects.
hasModifier()
hasModifier()
This is a simple boolean method that returns true if any modifiers for the given trait that extend modClass
are present on the given entity.
forEachModifier()
forEachModifier()
Performs the given action
on every modifier of the specified type
on the given player
.
A typical lambda Consumer
parameter for this method might look something like this:
In this example, the system will print "Hey, that's my TempModifier!"
for every instance of MyTempModifier
that is found on the entity.
Data Storage
Each TempModifier instance has its own CompoundTag that can hold extra information for the TempModifier. NBT should be used instead of instance fields if possible, since NBT is saved when the entity is unloaded, and can be synchronized with Temperature.updateModifiers()
.
Note: This data is not synchronized automatically, like for ItemStacks or Entities. updateModifiers()
must be called if the data is needed on the client.
Registration
TempModifiers need to be registered in order to be properly applied, stored, and read. To do this, we need to hook into the TempModifierRegisterEvent
event.
This event fires when the server (or internal server in singleplayer) is loaded. It compiles all registered TempModifiers into a Map<String, TempModifier>
, which can then be used to look up a TempModifier
class based on its internal ID.
This event fires on the FORGE
event bus, and is not Cancellable
.
Syntax
In this example, we are registering MyTempModifier
by passing a TempModifier supplier into event.register()
. The supplier should always generate a new instance when called, or you risk having multiple entities share the same TempModifier instance, which will have very unpredictable side-effects.
event.register()
can be called multiple times for different TempModifiers if needed.
Registering a TempModifier multiple times will simply overwrite the existing TempModifier with the new one, which has essentially no effect.
Adding By Default
There may be a TempModifier that you wish to have applied automatically when an entity spawns. The standard way to do this is through GatherDefaultTempModifiersEvent
.
As the name suggests, this event will gather the "default" TempModifiers supplied by all mods (including Cold Sweat itself) and add them to the given entity immediately upon spawning.
This event is fired once for every temperature Trait, so ensure your TempModifier is being added to the correct trait.
This event fires on the FORGE
event bus, and is not Cancellable
.
Syntax
Notes:
This event is fired EVERY TIME an entity joins the world, even after relogging, changing dimensions, etc. Check to ensure you are not unintentionally adding duplicate TempModifiers.
This event fires after the entity has been constructed from NBT, meaning any TempModifiers it previously had (if any) will be loaded by this point.
It is discouraged to use
EventPriority.HIGHEST
, as this is when Cold Sweat's default TempModifiers are applied. Adding TempModifiers before this point might cause issues.
Last updated