Attribute Functions - CleverNucleus/data-attributes GitHub Wiki

Data Attributes introduces two entirely new ideas to entity attributes: functions and properties. Attribute functions are best explained using examples:

Attribute functions allow you to relate attributes to each other in a way that is common in other games: Dota 2 has the primary attributes Strength, Dexterity and Intelligence - where strength increases the health pool, dexterity increases armor and intelligence increases mana; PoE is much the same, where strength increases maximum life, dexterity grants evasion and intelligence also increases mana.

The implementation of attribute functions is out of the scope of this wiki, but basically each attribute now has a collection of children and parent attributes. When defining an attribute function, you define an entity attribute (the parent) and assign to it a list of other entity attributes (children). The resulting behaviour in game is that whenever the value of a parent attribute changes, the value of all its children changes as well (important to note: this is one-directional, a change in the value of children does not impact the value of their parents). This can be difficult to understand using descriptions alone, so below are some example cases:

Example 1

Say the player has the attribute constitution; and for every point added in constitution, a point is also added to max health. This would be done using an attribute function. Functions allow for an attribute to influence another attribute (or a collection of attributes), which can then also have functions.

Example 2

Using Example 1 as a reference, this example shows how we might implement it. Say we have our examplemod, and we've added the attribute Constitution (see Overrides), but now we want to make it so that for every point added to constitution, a point is added to max health and half a point is added to armor.

We go to the directory data/examplemod/attributes/ where we create the json file functions.json. Inside this file, we add the following:

{
    "values": {
        "examplemod:constitution": {
            "minecraft:generic.max_health": 1.0,
            "minecraft:generic.armor": 0.5
        }
    }
}

That's all that is needed. Now whenever a point is added to Constitution, a point is also added to Max Health and half a point is added to Armor. Note that the value assigned is a multiplier. If we added +5 to Constitution, we would add +(5 * 1.0)=5 to Max Health and +(5 * 0.5)=2.5 to Armor.

It is important to note that attribute functions are implemented differently in 1.17.x and 1.18.x. In 1.17.x when a modifier was applied to a parent, the same modifier would be applied to the children. Now in 1.18.x when a modifier is applied to the parent, only the change in value is carried over to the children, meaning that you can apply the same modifier to both parent and children, whereas before you could not.

Recursion

The keen eye among you may have noticed that there is an opportunity for recursion/infinite attributes. Lets show how this might be done:

{
    "values": {
        "examplemod:constitution": {
            "minecraft:generic.max_health": 1.0
        },
        "minecraft:generic.max_health": {
            "examplemod:constitution": 1.0
        }
    }
}

Looking at our functions.json file, we can see that when we add a point to constitution, a point is added to max health; but when a point is added to max health, a point is added to constitution - "it would seem we have a vicious cycle on our hands". One could even see how a huge chain of attributes could accidentally become recursive. Constitution adds to Max Health adds to Strength adds to Armor adds to Knockback Resistance adds to Constitution... Not to mention that since each attribute can have many functions attached, the tree of possible combinations resulting in recursion can be extensive.

Therefore, Data Attributes implements protections against this. When the functions.json files are read and assembled, the functions tree is scanned for loops. Any loops found are broken by removing the first function found that results in a loop. The specific function that would be removed for a specific set of circumstances is unreliable, just that it is impossible for loops to form. This is done so that checks do not have to be made in game, which could cause lag.

That being said, developers and pack creators alike should still be careful not to create loops.