Last updated: January 30th, 2019. Latest version here.

Components

As explained in the essentials guide, components are reusable functionality that defines behaviors for entities. You can find the Lua components under stonehearth/components.

Designing a component

Components should ideally be collections of re-usable functionality. When deciding how to structure functionality associated with an entity, keep some things in mind:

  • Can this component be made more generic? For example, both beds and firepit seats require a reservation mechanism, so only one person can use each piece of furniture at a time. Instead of creating a bed reservation component and a firepit seat reservation component, we create one lease component shared amongst all lease-able things.

  • Is this component containing only read-only data? If so, consider putting some of that into "entity_data", instead of "components". You can access that data from inside the component later, for example:

    local foo = radiant.entities.get_entity_data(chair, "stonehearth:appeal").appeal
    

Adding new components

All Lua components are classes, and look something like this:

  local MyComponent = class()

  -- The initialize function is always called
  function MyComponent:initialize()
    -- The json variable will contain the associated component data 
    -- from the entity's .json file
    local json = radiant.entities.get_json(self)
    -- The saved variables on the component must be declared
    -- in the initialize function. Even nil variables!
    self._sv.first_saved_variable = nil
    self._sv.second_saved_variable = 'test string'
    self._sv._unremoted_save_variable = json.my_json_data_variable
  end

  function MyComponent:getter_or_setter()
  end

  function MyComponent:other_functions()
  end

  return MyComponent

iconAll components have a self._entity field that is the entity that the component is created on. We can use saved variables and lifecycle functions in them.

You can add Lua implemented components to JSON files by giving the name of the component (prefixed by the mod's namespace plus a colon) to the "components" section of the entity's JSON file. For example, this is the workshop component (implemented in workshop_component.lua) from carpenter_workbench.json:

  "components" : {
     "stonehearth:workshop": {
        "job_alias": "stonehearth:jobs:carpenter"
     }
  }

Any data that is given to the component in the JSON file can be retrieved using the call radiant.entities.get_json(self).

Finally, to access the component, it must be referenced in your mod's manifest, inside the "components" section (which is at the same level than "aliases", "mixintos", etc). For example:

  "components" :  {
     "workshop" : "file(components/workshop_component.lua)",
     "crafter"  : "file(components/crafter_component.lua)"
  }

We can also create client components, which are treated like client controllers. We'll declare them under "client_components" in the manifest, instead.

Using components

Once you have a reference to an entity, usually generated by the creation of the entity, you can then access its components through:

  local comp = entity:get_component('stonehearth:workshop')

For the Lua components, the name that we pass will be the namespace of the mod, a colon, and the component's name as defined in the manifest (same structure than in the entities JSON files). Note that the C++ components are used without a namespace (for example the mob and render_info components).

You can then perform any actions on the comp variable that are implemented on that object. For example:

  local workbench = comp:get_entity()
  comp:start_crafting_progress(order)

When to use "get_component" vs "add_component"

Use get_component() when you need to test for the presence of a component. This is typically used to see if an entity plays a certain role. For example, the entity is used as a mining zone if this returns a non-nil value:

  entity:get_component('stonehearth:mining_zone')

Use add_component() when the component must exist and it will be created if necessary:

  entity:add_component('stonehearth:mining_zone'):get_region()

Component renderers

A component renderer is what you would use to create some kind of visual representation related to a component.

Stonehearth doesn't really render entities per se, because entities aren't really anything per se - they're bags of components (data and functionality). So, if you want something visual to show up, you need a renderer (or two!) attached to that object, and that means a component renderer.

(Caveat: hearthlings and goblins and deer and etc. don't appear to have renderers associated with them, but really they do - they're in C++, though, not Lua. They're added by virtue of those entities having render_info components added to them).

A component renderer will get initialized on the client with the owning entity and component; it's then up to the component to figure out what it wants to render and how. A good example of a simple component renderer can be found in stonehearth/renderers/stockpile/stockpile_renderer.lua. There, we take information about the stockpile from the stockpile component (the size of it, as well as the items inside it), and use that to construct the 2D stockpile area that's visible in stockpile placement mode.

A more complicated example would be structure_renderer.lua, which deals with the various rpg/cutaway vision modes for building, constructing multiple renderable things for both the visual of the structure, as well as its shadow volume (so that even buildings with walls cut away cast appropriate shadows).

Component renderers are declared under "component_renderers" in your manifest (same level than "aliases", "components", etc). They have the same name than the component they render. For instance:

  "components" : {
     "stockpile" : "stockpile": "file(components/stockpile/stockpile_component.lua)"
  },
  "component_renderers" : {
     "stockpile": "file(renderers/stockpile/stockpile_renderer.lua)"
  }

You might also see a section in the stonehearth manifest called "invariant_renderers", but modders don't need to worry about it.