Buffs

Buffs (both positive and negative) are temporary changes to an entity's attributes. They are applied in response to a situation or event. For example, sleeping on the ground may add the groggy buff to a hearthling, causing them to be slower for a few hours after waking up.

The buffs component is used to add, remove and keep track of all the buffs/debuffs that a hearthling currently has. The buff data that we declare in a JSON file is then managed by the stonehearth/components/buffs/buff.lua controller.

The existing buffs can be found inside stonehearth/data/buffs. There might be some more in subfolders of stonehearth/jobs.

Creating buffs

We can easily create buffs that modify some attributes from the hearthlings with just JSON, or do some more complex things with a small Lua script. Don't forget to add an alias for your buff in your manifest!

These are the properties that we can find in the buff's JSON file:

  • "type" : "buff" -- we declare that this file represents a buff.

  • "axis" -- either "buff", "neutral" or "debuff". This will positionate the buff icon in different rows in the unit frame when we select the hearthling.

  • "display_name" -- a localized name for the buff to display in its tooltip in the UI.

  • "description" -- a localized description for the buff's tooltip.

  • "icon" -- an icon for the buff.

  • "duration" -- a time expression for the duration of the buff (in-game time). The buff will be automatically removed once this duration ends. If this field is not included, the buff will be permanent.

  • "persistent" -- whether this buff should be stored when the game is saved or not; leave it as false if the logic to add the buff is run by other scripts on load. When true, it will be reapplied automatically on load by the buff controller (buff.lua).

  • "repeat_add_action" -- what to do if the buff is readded when it hasn't finished its duration yet. It can be either "renew_duration", which will restart the duration, "extend_duration", which will sum the duration of the buff to the current time left, or "stack_and_refresh", which will add a stack to the buff and restart its duration.

    If this field is not included, the buff will continue with its current duration until it expires.

  • "max_stacks" -- used only when "repeat_add_action" is "stack_and_refresh". This is the maximum number of times that the buff can be stacked (each stack will reapply the modifiers on top of the current values).

  • "cooldown_buff" -- optional field, points to an URI of a buff that will get applied when this buff's duration expires. While the cooldown buff is active, you won't be able to reapply this buff. You can see an example in the hothead buff (furious/winded buffs).

  • "invisible_to_player" -- optional field, false by default. When true, the buff will not appear in the unit frame when we select the entity. This is useful if what the buff does should not be conveyed to the player (e.g. if it just sets a flag for use in other scripts, etc. For example, we use a swimming buff to change the hearthling's posture when they're in water and make them move slower).

  • "effect" -- optional field. URI of the effect that will run when the buff is added (for example, the stamina tonic makes an orange cloud of particles to appear on the hearthlings for a second when it is used).

  • "modifiers" : {} -- here we can modify attributes and stats. They are defined like for traits, only that the key here is called "modifiers", not "attribute_modifiers".

    For example, if we add this in our buff's JSON file:

    "modifiers" : {
       "speed" : {
          "add" : 20
       }
    }
    

    in the game we'll see it when hovering over the attribute in the Attributes tab of the character sheet. That way we know which buff is modifying the attribute and how much: attribute_modifiers Both attribute modifiers and properties will be automatically undone if the buff/trait gets removed.

  • "properties": [] -- optional field. An array of properties that we can use in scripts. Same than for traits.

  • "script" -- optional field. URI of the script that will run when the buff is added and removed. You can reuse existing scripts from the stonehearth mod too. Look for buffs that are close in functionality to what you want to implement. The script structure is as follows:

    local MyBuffScript = class()
    
    function MyBuffScript:on_buff_added(entity, buff)
       local info = buff._json.script_info
    end
    
    function MyBuffScript:on_buff_removed(entity, buff)
       -- Here we can clean up stuff created by the buff
       -- E.g. destroying timers, listeners, etc.
    end
    
    return MyBuffScript
    

    The entity argument is the entity to which the buff has been added/removed and the buff argument represents the buff controller, so we can retrieve info from the JSON file as shown above.

  • "script_info" : {} -- if we declared a script for this buff, we can define data for it here (actually we can include any field in the JSON file to retrieve it in the buff's script, so this field could have been called "data" if you wanted).

  • "injected_ai": {} -- we can inject AI through buffs. The syntax is the same than when injecting AI via an equipment piece.

  • "set_posture": "stonehearth:swimming" -- we can set the posture of the hearthling like this. The key of the posture is defined in the postures.json file from the entity's rig.

You might see a "tags" property in some of the existing buffs. It's no longer in use, so you can omit it.

Applying and removing buffs

There are several ways to apply buffs to entities:

  • Via "injected_buffs" property in an equipment piece. This will apply the buff as soon as the equipment piece is equipped by the entity. Normally we use buffs without duration for this case, so that they only expire if the equipment piece is unequipped.

  • Via "inflictable_debuffs" property in the entity_data of an entity (e.g. a monster) or of an equipment piece (a weapon, normally). For example, undead have a 50% chance of inflicting this debuff while they're fighting:

    "entity_data": {
       "stonehearth:buffs": {
          "inflictable_debuffs": {
             "infection": {
                "uri": "stonehearth:buffs:undead_infection",
                "chance": 0.5
             }
          }
       }
    }
    

    Remember that here we're not adding the buff to the entity that has this entity data, but to other entities hit in combat.

  • Via "inflictable_debuffs" property in combat attacks. Same syntax than above (an identifier linking to the URI of the buff and the chance).

  • By using a consumable item: some of the tonics have a command in them that runs a function when we click on it. This function looks for the following info from the entity_data of the consumable:

    "stonehearth:consumable": {
       "script": "stonehearth:consumables:scripts:buff_town",
       "buff": "stonehearth:buffs:courage_tonic"
    }
    

    The script will apply the buff to all the hearthlings. You can reuse it or use it as an example.

  • In a Lua script (we can also pass an extra parameter with options):

    entity:get_component('stonehearth:buffs'):add_buff('silent_woods:buffs:spooked')
    -- Also possible:
    radiant.entities.add_buff(entity, buff_uri)
    

Buffs are removed when:

  • Their duration expires.

  • The equipment piece that applied them is unequipped.

  • In a Lua script (we can also pass an extra parameter as a flag for removing all the stacks):

    entity:get_component('stonehearth:buffs'):remove_buff('silent_woods:buffs:spooked')
    -- Also possible:
    radiant.entities.remove_buff(entity, buff_uri)
    

Testing buffs

We can easily add buffs to check that they're working as intended, even before hooking them to an equipment piece or in a script.

Once we add an alias for our buff in our manifest, we can select a hearthling in the game, open the default console and type:

  add_buff my_mod:buffs:my_buff

and press Enter. We should be able to see the icon of the buff next to the unit frame of the hearthling. If the buff is invisible to players, we can open the Object browser, click on the value for the buffs component, and check that it's listed there.

We can also remove a buff in the same way, using the remove_buff command.