I’ve been looking into coding recently, and while my experience in SH coding is minimal, I’m eager to try.
More precisely I was looking into decorations doing something on a button click. Things that seem similar to me are the locking iron door and archer switching arrow types. In both cases I saw entities (door and archer) having certain action buttons added, which trigger some action when clicked (trigger_lock for door and swap ammo for archer). I didn’t however found where those actions are coded.
A little help here?
@8BitCrab , any idea whom should I tag?
In the stonehearth manifest, you can see a section named functions{}. That is where most buttons get their code from.
Thanks!
From what I see, there are short code fragments called “call_handlers” that usually call LUA functions described in /components. Will dig there.
More intel. While sifting through /docs in stonehearth.smod I’ve read docs on components. Seems like that is exactly what I need. It’s getting a little clearer now.
Some theory first:
Components are LUA classes which are integrated into our entity and represent different functionalities associated with it. Thus, every component has saved variables that persist between save / load and functions to do something with this component (and its saved variables).
Saved variables can be either initialized from a JSON describing the component or with LUA commands inside the component itself. That’s what bugged me - I thought JSON is used as a permanent storage while in fact it is only used for initialization.
Now if Radiant used incapsulation (and I suppose they did, because why not?), components can’t modify other components (or can do it only via some public “interface”). In general they are standalone code fragments, and changing / adding one should not influence (or break) the others.
Now off to some practice:
function LampComponent:initialize()
local json = radiant.entities.get_json(self)
self._light_policy = json.light_policy
self._light_effect = json.light_effect
self._tracked_entities = {}
end
This is a constructor for lamp component. It reads light_policy from the JSON describing our lamp entity.
function LampComponent:activate(entity, json)
self._render_info = self._entity:add_component('render_info')
self:_check_light()
if self._light_policy ~= "always_on" then
self._sunrise_listener = stonehearth.calendar:set_alarm('6:00+5m', function()
self:_light_off()
end)
self._sunset_listener = stonehearth.calendar:set_alarm('22:00+5m', function()
self:_light_on()
end)
end
end
And this one is an… activator? Which creates a timer based on light_policy (or permanently switches a lamp on if the policy dictates it).
Of course, I ummediately have some “stipid” questions, like
- When is the activator function triggered?
- Is constructor called on entity crafting only or on entity load, too? (intuitively I suppose it should be called on crafting only but I may be wrong).
initialize() is called only once, at the exact moment the entity is created (starts existing in the game)
activate() is called every time the saved game is loaded, and after the initialize() if the entity just got created.
Thanks!
To begin with something simple, I’m trying to add to my lamps a button that will toggle light_policy on and off.
As I see with your explanations, _light_policy (which should be a saved variable since it starts with _) is initialized only once - on item creation. To make it changeable I should
- Add a function to toggle it to the component
- Create a new command triggering that function (and add it to my mod manifest)
- Reference that command in the lamp’s JSON to get a nice button in my UI.
Time to try it
I’m really not sure about this. I didn’t checked further, but I’m under the impression that variables starting with “_” are simple private variables.
Those that will be saved are at the self:__sv.your_variable
Not sure though! Someone else could check this later.
offtop:
rescue_citizen_action.lua
--[[
Task that represents a worker bringing a piece of wood to a firepit and setting it on fire.
]]
They copy pasted from the light_firepit.lua to speed the coding
Ayep. I hope my hearthlings won’t set anyone on fire.
Things appear to be more complicated than I thought.
Commands can be of two types - those that have an action “fire_event” and those that “call” functions.
I can’t find anything on events, but I found where are those functions they call. Unexpectedly, not in the component. Or, more precisely, in the component, but they are not called from a command directly.
{
"type": "command",
"name": "call_trader",
"display_name": "i18n(stonehearth:data.commands.call_trader.display_name)",
"description": "i18n(stonehearth:data.commands.call_trader.description)",
"disabled_description": "i18n(stonehearth:data.commands.call_trader.disabled_description)",
"icon": "file(call_trader.png)",
"action": "call",
"function": "call_trader_command",
"object": "stonehearth.shop",
"args": [
]
}
The “call_trader_command” is not situated in the shop_component. Instead it is a proxy situated in /services/server/shop/shop_service.lua. This function is the one that finally addresses the component and calls its function.
Why is it done like that, I have no idea. I suppose Radiant had their reasons. Maybe it is something related to multithreading.
PS.
function frostfeast:_three_headed_monkeys()
local victim = radiant.mods.require('stonehearth.services.server.game_master.controllers.encounters.script_encounter')
The thing I love about programmers is their sense of humor.
Edit: found this comment by @sdee on firing events. It’s horribly outdated, but I now suspect events are handled not through server/client services but through call_handlers. The only thing that confuses me is that event name in command JSON and in manifest is different. For example, where JSON uses “radiant_place_item”, manifest lists only “place_item” function (that references the appropriate call handler).
Edit2: boop @RepeatPan. Because I can. Maybe he knows something.
Edit3: there are not 2 but 3 types of commands.
- firing events
- calling function on object (service)
- calling function from manifest (stonehearth:function_name)
First of all, there’s multiple object creation callbacks. One of the files of frostfeast describes them in detail (heat_source_component.lua), but on top of my head, the class system with the component flavour attached:
-
initialize
is always called when the class is created. Think of it as your classical constructor. -
create
is called the first time the component is created on an entity. I’m not sure if this is relevant outside of components. Called afterinitialize
-
restore
is analogue to create, except when loading a save game. They’re mutually exclusive;restore
is always called when loading from a save,create
is always called when initially created. Called afterinitialize
. -
activate
is called aftercreate
orrestore
-
post_activate
is called when all other components on this entity have been activated. This is only really relevant when you are loading the game, as this ensures the other components have been activated as well. When the component is being created at runtime, the other components (likely) already exist. -
destroy
might be called when the component is removed. This might also occur when the entity is deleted, as the components are removed then, but I’m not sure.
Normal SH components sometimes do something like self._restore = true
to figure out whether they’ve been restored or newly created in activate
/post_activate
. It’s a handy trick if you need to do some special stuff.
Now, saved variables are explicitly within self._sv
(for components, this is done for you; for everywhere else, you need to make your own saved variables stuff and wire it up - frostfeast does this for its services in frostfeast_server.lua, as does normal stonehearth in stonehearth_server.lua). It’s important that you need to access the variables within initialize
, because the saved variables need a “schema” (at least on the first level), otherwise you’re going to get an error message.
Plus, remember that there’s a difference between self:foo()
and self.foo
, period vs colon. A colon is a function call (self:foo(bar)
is analogue to self.foo(self, bar)
) and may only be used for function calls on an object (basically, you’re passing the thing you’re calling it on as first parameter. For fun, try ("Hello %s!"):format("world")
).
What you want to look at is likely the stonehearth_client.js somewhere around line 101 (doCommand
). It’s the whole logic, I suppose, for the whole thing - including fire_event
and call
. fire_event
seems like it’s calling an event in JavaScript, while call
calls something either as a lua function (see resource_call_handler.lua as example, or the call handlers within Frostfeast), or a member of some “global service” if object
was specified (for example, stonehearth.shop
).
You are right that call_trader_command
is a “proxy”, but also not quite. It’s not a real proxy, the call trader is instantiated as a service within stonehearth, given the name stonehearth.shop
(on the global variable, which represents the mod stonehearth, called stonehearth, there’s a member/field that’s called shop
- it’s basically just a table with mod relevant data, for Stonehearth, it contains the services, but every mod is kinda free to do what it wants with that). Instead of going through the whole call handler/function bit, it’ll just straight up call the shop instance (or rather singleton) and pass it the arguments (you can get at least the entity it was called on by default).
lua doesn’t do multi threading at all, but there’s two distinct lua states (server/client) that are independent of each other, hence why you have to define the side in the manifest.json (so the call gets routed properly).
What you want, probably, is:
- A callback function (check out Frostfeast’s present_call_handler.lua for a very minimalistic approach)
- A manifest.json entry so it’s loaded (again, Frostfeast manifest) - you probably want this on the server side, unless you’re doing some super advanced client rendering (which I heavily assume you’re not)
- A command.json (again, Frostfeast, although I’m somewhat sure that
{{self}}
doesn’t do anything anymore) - An entry/mixinto in your object that adds said command to the entity.
That’s what I was going to try… more or less. But now when you’ve explained this “inner kitchen” a bit, I feel more confident. I usually have trouble using things when I don’t know how they work, at least on a general level.
Although I didn’t know about period and colon. I got used to colon representing referencing subelements (like variables in a class).
Also your insight about creation callbacks is very helpful, thanks!
Perhaps
print(("Hello %s!"):format("world"))
?