Mixinto into LUA?

Hey there!

I’ll try to be short today :3
Is it possible to mixinto a Lua file? I’m trying to make an item similar to the Hearth/Firepit - that when the night comes someone will take some fuel and light it up. However after I manage that, I’ll try changing some details about it so I created a new component instead of just using Firepit component…

This started a series of issues, however, that I started sorting out one after the other until I got to a point that I don’t know what is wrong anymore :joy:
I basically had to create an action for my component however that action couldn’t be handled by anyone because it’s based on the light firepit action which is part of the placement task group… Task groups are LUAs though - at first I tried overriding it with my own placement task group which was the same but with my action added and it didn’t work. Then I tried creating my own task group and I started getting an issue about this task group not being in town… I then created mixintos for all professions + the player_task_groups file adding my task group to all professions and I still get an error saying my task group is not in town :frowning:

Any ideas? Is there a way to just (successfully) mix my action inside the Placement task group without having to create a new task group and mixing it into everywhere?

Thanks :slight_smile:

The Smart Crafter mod helped me with mixing in some Lua; I’m not sure if it will be helpful to your situation though.

I don’t know, but I’m interested myself (because I want the fuel used by fireplaces like this to change to firewood instead of logs) so I’ll monitor this thread with interest :slight_smile:

I believe it is impossible to “mixin” to Lua the same way you mixin to JSON files. There are two alternative ways.

1 - you can override the entire Lua file. If you only want to change one line of code, that means you need to include the entire file with that line changed in your mod. I do this to adjust mining priority for the Miner.

2 - you can do some fancy stuff to mixin specific new functions to some Lua classes. I don’t know all of the places this works, but I believe you can at least do this with services. There’s a lot of extra work involved in doing this (incl. writing a starting script), and the benefits of this method over overriding are dubious.

For your purpose, I recommend using the first method. It’s inconvenient, but I think it’s your best bet here.

Yes, but they are loaded through json, like actions. For example, my fisher has unique tasks, and it is included in his job as:

"task_groups": [
	"archipelago_biome:task_groups:fishing",

So you can do that and included your task to a specific job or you can also included it through ai_packs, lie so:

{
	"type" : "ai_pack",
	"task_groups": [
		"archipelago_biome:task_groups:crab_movement"
	]
}

@Moai & @BrunoSupremo

Hmmmmmmm… As I said in my first post, I did try overriding the “placement” task group and it failed - and I did try adding my new task group to all classes and the workers and it also failed;
I kept getting the “task group not in town” error when the action was called.

So there’s either something I’m missing OR I might’ve types a wrong path on the override, etc… I’ll try it again today and will reply here :slight_smile:

Thanks for the replies!

The fuel used by the Firepit is on the Firepit component if I recall correctly. You could just override it with the same file but using your fuel without having to create new components and AI, perhaps?

Not with that attitude; that’s for certain.

The method used to do that sort of stuff is called monkey patching, amongst other names. You can see an example in (old) Frostfeast’s code:

function frostfeast:_three_headed_monkeys()
   local victim = radiant.mods.require('stonehearth.services.server.game_master.controllers.encounters.script_encounter')
   victim.start = function(self, ctx, info)
      if info.script then
         local script = radiant.create_controller(info.script, ctx, info.data)
         self._sv.script = script
         radiant.verify(script, "script encounter could not find script %s", info.script)
         script:start(ctx, info.data)
      end

      -- Override: Allow the script to define the next campaign
      if not info.script_triggers_next_encounter then
         ctx.arc:trigger_next_encounter(ctx)
      end
   end
end

lua functions are variables, lua classes are tables (using metatable magic). Therefore, you can re-assign them as you please. If you know what you’re doing, you can get quite far. In Stonehearth, it’s usually possible to monkey patch almost every single lua object and services, with the exception of some stuff that is running at the very beginning (and therefore before your code).

In the code above, I’m patching the start method of the script encounter. I’m doing a full replace of the method, but you could also do partial replaces, like this old, outdated example from the cookmod:

local old_fnc = stonehearth.game_master.start

stonehearth.game_master.start = function(self, ...)
  local ret = { old_fnc(self, ...) }

  self:_start_campaign('other')
  return unpack(ret)
end

I’m calling the original method, save its return values, then do my stuff and return the values, basically wrapping the old method with my code.

When I messed around with the firepit, back in FF15 I think, I wasn’t able to find a nice way to patch it though - it was too deeply ingrained if I remember correctly. So I overwrote the component, and since big mods weren’t a thing back then and Frostfeast kind of was a complete overhaul anyway, I took the liberty to allow myself that.

But technically speaking, you could get the component (require the necessary file, take a look at the class that is returned, then override the specific method). In that case, it might just work.

2 Likes

Well, I’m still learning my ways but I think I can understand the logic of what you’re saying! I’ll definitely experiment with it, thank you!

PS: Now I know why you’re the “Scripting Wizard” :smile:

1 Like

One last bit here: I’m a big fan of monkey patching, because it really allows you to basically screw with whatever you want. Usually.

If you take a look at the old rp mod loader that I wrote back in the days of the very first Alpha, you’ll see lots of overrides for all kind of things. This was in a time where neither overrides nor mixintos were a thing. Ah yes, the good old times. Scripts were still scripts back then, not like the ones you get these days, I tell you. Now get off my lawn.

However, as for the statement that the benefits are dubious - this is debatable. Technically, if done properly, two mods can patch the same function and still work properly. If you use override, it’s anyone’s guess whose mod wins the override battle. It also allows for more granular changes, which means that your code has a higher chance of surviving updates and other mods.

I suppose if I had to give you advice, it’s “use it as sparsely as necessary, and as often as required”. Use it when you need it to, and when there’s absolutely no other way to do it. Sometimes there are other ways, which are a bit nastier, but more “official” - they are usually better suited.

Edit: If you do decide to start diving into this, keep in mind that instances and classes are separate things, especially when it comes to unclasslib. If done “wrong”, you’re merely patching one instance - not the whole class. Although that can also be what you want, in certain situations, it usually isn’t.

4 Likes

Perfectly fair. I’m not a big fan of pre-optimizing; I like to solve the problem when it becomes a problem, rather than before. Until it becomes a problem, I generally don’t touch it.

I’ve seen your monkey patching script, and that’s what I was referring to with my method #2, where you’d run a startup script in your mod to inject whatever new classes you want. You’re right, you could also change existing things. I didn’t really think about that, but it makes sense now that I think about it. Ideally, this should be a function that ships with SH. Right now, when Stonehearth isn’t drowning in thousands of heavily scripted mods, I don’t see the need to apply the extra effort. The logical beauty of mixing things into Lua is offset by the ugliness of attaching the same script to every mod you develop.

The problem is about defining best practices and adhering to it. It doesn’t even need to be “heavily modded”, anyone can override any lua file for whatever reason they want. As someone who had the pleasure of trying to play nice with other mods, it’s a nightmare.

This isn’t about optimisation as much as it is about architecture. Sure, you can say “I don’t need a front door, the neighbors are nice anyway” - up until the hamburgler shows up and empties your kitchen.

If you get into such a situation, it can get really nasty, too. If two mods override the same file, who is going to monkey patch, and who isn’t? “My mod was around longer”, “My mod has more downloads”, “I need more changes” and so forth - it can devolve pretty quickly unless one party readily says “Alright, I’ll do the patching” - but at that point, it might have been done from the very beginning.

3 Likes

But I don’t know what I’m doing. And making that work :’)

4 Likes