[Tutorial] Adding custom creatures

A few things:

  • Use your mod as instance, i.e. have the callback on critter_ex. Local functions and the event system do not play nicely together all the time, especially if you use anonymous functions.
  • Using radiant:init as event is somewhat dangerous, as it’s called per mod after said mod is completely loaded. radiant:required_loaded (on radiant, not the mod object) is called once all mods have been loaded. Technically, you can’t say when radiant:init is called (dependent on however SH loads mods), but you can say when radiant:require_loaded is. If you’re doing anything that requires another mod to be loaded, radiant:init won’t work. In this case, because we’re patching files directly and not an instance, it works I suppose. But it’s definitely something to watch out for.
  • I would suggest proper indenting for the example code - it makes it much easier to read for lua beginners.
2 Likes

I’ve never actually had any problems with using local functions together with callbacks, maybe it causes some rare bug that I’ve yet to encounter. Anyway, I’ve changed the example code to your suggestion.

Ah, so that’s why you had 'radiant:required_loaded' in there. I was hesitant to use it as I’ve not seen it being used anywhere, at least not in the stonehearth mod. It’s good to know the difference, and yes in this case it works as I created two separate mods using this method (one for the mandragora and the other for the golem).

The indentation was weird. I did have proper ones, but discourse’s [code] tag doesn’t like new-lines. Sometimes when I used them it messes up the indentation, and sometimes the new-line isn’t even registered. Anyway, removing all new-lines fixes this, it doesn’t look as nice as having something to separate each line but at least now it’s indented.

1 Like

Maybe it was changed, or maybe it was in a different part altogether, but at some point, passing a local function in the event system caused said function to never trigger (because it was garbage collected, or something like that). In any case, grouping your functions is never a bad idea. In my opinion, local functions are only to be used for “helpers”.

In any case, that’s the official way of doing it (see about every other component/AI part/observer whatsoever). If we’re teaching others how to lua, we might as well do it in a proper fashion.

That’s true enough. Those do build up and return a class each. Though the server / client init scripts don’t do it that way, they instead return a table of all their respective services. So that would be my excuse as to the previous solution. Anyway, the current one works at least just as well so I see no reason to not use it.

Really cool you’re putting your time into this. Really great help for the community.

2 Likes

Hmmm…ok works if i wan’t to add two or three critters but i have to rewrite the lua with every trappable critter i wan’t to add. Correct ?
Is there a way to add a function to read the critter.json which adds a critter to the table ?
Let’s say if i give the critter the component “spawnable” = true

That would be a lasting solution to the problem if i wan’t to add a snowhare, a tiger to elephants and dinosaurs

Yes, you’d have to add table.insert(self._spawnable_critters, 'my_mod:critter') for each critter you wanna add. Another way would be to store all your critters in an array and then do a loop which adds every critter, like so:

local critters = {'my_mod:critter_1', 'my_mod:critter_2', 'my_mod:critter_3', 'my_mod:critter_4'}
for _, critter in pairs(critters) do
   table.insert(self._spawnable_critters, critter)
end

It functions the same, but looks a bit cleaner.

Ideally, yes. Some solution similar to that would be the best one, unfortunately the source code would have to rewritten on numerous places, which is a bit of a hassle for a mod to do. So the easiest way, currently, is to change up as little of the code as possible and still get the desirable result, which is what we’re doing here. If we change the code in more places, the risks of the mod being out of date when an update hits will be even greater, and we want to minimize that risk as much as we can.

We’ll have to be patient and wait for Radiant to change it later themselves. Until then, we’ve got this solution to use instead.

1 Like

Because the idea is still to be object oriented. Currently, they only have radiant and stonehearth, both which have no real need to use any events directly inside the mod. However, it doesn’t make sense to have stuff as local function and as I’ve already said, I’ve been told that you’re somewhat supposed to use the object approach instead of local/global/anonymous functions (which therefore aren’t really officially supported - or at least, less supported than any other part of the API).

It ties your mod nicely together and allows other mods to patch it, if necessary. If you’re declaring it as a local variable, you’re making it pretty impossible for other mods to extend/patch your mod. It wouldn’t be clean in any way and should be avoided at all cost, but maybe there’s no other way or it’s the cheapest - there’s always a scenario where that would be handy.

In any case, you’re teaching wrong patterns. You’re confusing people by using local functions here, object-based approaches there, and a mix of both over here. The code should be consistent and if that requires some callback function that’s only ever called once and that’s by the events system on top of it, then so be it. It’s better than giving people the idea that events have to be local functions… or other nightmares that might arise.

1 Like

That makes sense. One should be consistent with teaching stuff. It was easy for me since I mostly understood what was going on, but not really thinking that I was showing several ways of doing lua. Which, as you said, would mostly serve to confuse people. I’ll be more careful in the future.

1 Like

Hey guys! Thought it was high time to put one of these up, so here you go; enjoy!

2.3 - Mastering ambient threats


Here you’ll learn how to add creatures as ambient threats. We’re gonna use the game master to do this, but we’re only going to scratch the surface on the game master as there’s a lot of it to cover just for this section. You should also take a look at Stephanie’s video on how to mod with the game master; check it out over here.

What we want to do with the game master (and because we’re using our own kind of creatures) we also have to implement our own population, so we’ll be touching on that subject a bit also.


2.3.1 - Populations


Populations are fairly straightforward to implement, though the population you’re about to see is only a small part of what it could contain, but it’s enough for what we want. So let’s see what’s in a minimal population definition:

{
   "kingdom_name" : "Spiders",
   "kingdom_id" : "spiders",
   "amenity_to_strangers" : "hostile",

   "roles":
   {
      "minion":
      {
         "male":
         {
            "uri" : [ "ambient_gm_mod:spiders:minion" ]
         }
      }
   }
}

As before, let’s go over it in detail.

  • "kingdom_name" - The name of the population (obviously), it’s used to get an easy to read text for the player.
  • "kingdom_id" - Another name of the population, but this is used internally and should be kept as a single word only.
  • "amenity_to_strangers" - How the creatures from this population acts when approaching creatures from other populations. It’s completely optional, and is by default set to “neutral” (a third option to use is “friendly”).
  • "roles" - This is where all the creatures are defined.
    • "minion" - The name of our role.
      • "male" - Gender! Obviously there’s also a female variant, but if you only have one gender for your role you should always choose male even if they would be considered female. This is because, when creating a new creature, the game chooses one of the genders randomly, and if the gender it chose isn’t available it will choose male by default.
        • "uri" - An array pointing to the creatures’ definition files that are connected to this particular role.

As said before; this is only a small part of what a population definition can contain. You can have it contain names for each role, names for its town, among other things.
If you want to see how to implement such things then I’d recommend that you take a look at an existing population definition file within the stonehearth mod, they’re located in stonehearth/services/server/population/data.

After this we’ll need to make sure that the population gets registered correctly, in a way that’ll make it an NPC population. We’ll create a new json file with the name “npc_index.json” which will be mixinto’d from the manifest.

{
   "spiders":
   {
      "kingdom" : "ambient_gm_mod:kingdoms:spiders"
   }
}

That’s all npc_index.json will contain, not really too hard to see what’s happening here; we’re defining a new population "spiders" (note: this is also the player_id which will be tied to this population) in which we have another key-value pair. The key is always "kingdom" so don’t change that, and the value is the alias to our population definition. Simple as that!

We also have to add all this to the manifest, but let’s take a look at it after we’ve added an ambient threat.


2.3.2 - Ambient threats


Right, we’ve set up a population so now we’re ready to create our encounters! We’re going to keep this simple and only have a couple of our own encounters for this, we’re also going to need a couple of files to mixinto to the existing ambient threats list. Let’s look at the encounters we’re creating first (with the first one being “spider_pack.json”).

{
   "type" : "encounter",
   "encounter_type" : "wait_for_time_of_day",
   "rarity" : "common",
   "in_edge" : "spider_pack",
   "out_edge" : "spider_raid",
   "wait_for_time_of_day_info":
   {
      "time" : "21:00+2h30m"
   }
}

Right, let’s go over this now.

  • "type" - As before, this describes what type this file defines, have this be "encounter" for everything relating to your campaign.
  • "encounter_type" - What kind of encounter this is. There are a handful to choose; we’ll be covering a couple only, but if you want to see what other types there are; you can find them in stonehearth/services/server/game_master/controllers/encounters.
  • "rarity" - Can be either "common" or "rare." As you can imagine, this says what’s the chance for this particular encounter will occur. It’s used when choosing between several encounters that share that same value from "in_edge."
  • "in_edge" / "out_edge" - These two decide where to go after an encounter is finished. When an encounter has finished its purpose it will look for other encounter’s "in_edge" value that shares the current encounter’s "out_edge" value. The first encounter in any campaign has its "in_edge" value as "start" though we’re not starting a campaign here so we don’t have to worry about that sort of stuff.
  • "wait_for_time_of_day_info" - All data that’s relevant to this encounter, it always has the name of the encounter type with the postfix “_info.” Other encounter types have different kind of data associated with them.
    • "time" - What in-game time this encounter will occur in. The interesting thing here is the “+”; what this means is that we’re setting a sort of interval of when this encounter will happen; so as to have some random variable, and not have it occur at the exact same time of day every time. In this example the encounter will happen sometime between the times 21:00 and 23:00.

That’s the first of our encounters, let’s go straight to the next file in line (“spider_raid.json”).

{
   "type" : "encounter",
   "encounter_type" : "create_mission",
   "rarity" : "common",
   "in_edge" : "spider_raid",
   "create_mission_info":
   {
      "spawn_range":
      {
         "min" : 80,
         "max" : 190
      },
      "mission":
      {
         "npc_player_id" : "spiders",
         "ctx_entity_registration_path" : "spider_raid.raiders",
         "role" : "pillage",
         "offset" : { "x" : 0, "y" : 0, "z" : 0 },
         "pillage_radius":
         {
            "min" : 0,
            "max" : 50
         },
         "sighted_bulletin":
         {
            "title" : "Eek! Creepy crawlies!"
         },
         "members":
         {
            "spiders":
            {
               "from_population":
               {
                  "role" : "minion",
                  "location" : { "x": 0, "z": 0 },
                  "min" : 2,
                  "max" : 3,
                  "range" : 30,
                  "scale_with_run":
                  {
                     "encounter_cap" : 1
                  }
               }
            }
         }
      }
   }
}

This is somewhat bigger, but let’s go over what’s new in here:

  • "create_mission_info" - As mentioned just before, this has the same name as the encounter type with “_info” stuck at the end. This kind of encounter starts a sort of mission for our creatures; a mission to destroy you! (well, that escalated quickly)
    • "spawn_range" - How far away to spawn from the player’s town.
    • "mission" - The information concerning this mission.
      • "npc_player_id" - The player id to use to find a population.
      • "ctx_entity_registration_path" - This isn’t really relevant for this section, but it’s good that you know about it. It’s pretty much a path to find the entities created from this encounter. Say, for example, that you’d want to destroy the entities from here, then you’d use this path to find them and then end them.
      • "role" - What role this kind of mission has. The other value you can use is "raid_stockpiles."
      • "offset" - An offset of where the creatures will spawn.
      • "pillage_radius" - The raiders will choose a point within this area relative to the player’s banner.
      • "sighted_bulletin" - The message that will pop up when a citizen spots the raiders.
      • "members" - Information of the creatures that will spawn.
        • "spiders" - A name for this particular member group.
          • "from_population" - It could be either this or "from_ctx", the difference is that with this one we’re using it will create and spawn new creatures, whilst "from_ctx" will use creatures which have been created previously.
            • "role" - The particular role of these creatures, this value comes from the population definition’s role value.
            • "location" - An offset for this member, each member type can have their own location values if you’d want them to not spawn to close to each other (as an example of usage).
            • "min" / "max" - How many creatures to spawn, it will be a random value between these two.
            • "range" - How big of an area these creatures spawn in.
            • "scale_with_run" - If the amount of creatures spawned will increase every time this particular encounter runs.
              • "encounter_cap" - A limit for how much this encounter can be scaled.

Note that we’re don’t have an "out_edge" value here, that’s because we don’t want anything more to happen after this encounter.

Right, now that we’ve looked at the encounters we want to make, let’s take a look at another encounter (“randomize_daily_threat.json”); though this is of one that already exists in the stoneahearth mod and will be used as a mixinto.

{
   "random_out_edge_info":
   {
      "out_edges":
      {
         "spider_pack" : { "weight" : 3 }
      }
   }
}

Not much to cover here, but let’s go over it as per usual.

  • "random_out_edge_info" - This is the info of an encounter type "random_out_edge", as mentioned just recently, this will be used as a mixinto so we don’t have to define an "encounter_type" or any of the other common values for that matter.
    • "out_edges" - All the different out edges that can occur, only one of the edges listed under here will be chosen.
      • "spider_pack" - The "out_edge"'s name (which has the same name as the "in_edge" of the first encounter we looked at).
        • "weight" - The chance of this particular out_edge to be chosen; the higher the value the greater the chance (the undead raid threat has a weight of 6).

And now we look at the final file (“ambient_threats_arc.json”) which will also be used as a mixinto.

{
   "encounters":
   {
      "spider pack" : "file(encounters/spider_pack.json)",
      "spider raid" : "file(encounters/spider_raid.json)"
   }
}

This file is really important as it tells the game what encounters there are to use; since we’re creating two new encounters we’ll only be listing those here, but if you were to create more encounters you have to add them in here as well.


We’re almost done, now we just need to define out manifest and we’re good to go! Before looking at the manifest though; here’s how the folder structure looks like for this mod example, to give you a reference for how the manifest will look like.

Yes, there are a lot of folders within the gm, but there’s a reason for that (trust me).

Right, now we can take a look at how the manifest looks like after all we went through.

{
   "info":
   {
      "name"    : "Ambient Threats example mod",
      "version" : 1
   },

   "aliases":
   {
      "kingdoms:spiders" : "file(data/populations/spider_population.json)",
      "spiders:minion" : "file(entities/spiders/minion)",
      "spiders:minion:teeth" : "file(entities/spiders/minion/spider_teeth.json)",
      "skeletons:spiders:minion" : "file(data/rigs/spiders/minion)"
   },

   "mixintos":
   {
      "stonehearth/data/npc_index.json" : "file(data/populations/npc_index.json)",

      "stonehearth/data/gm/campaigns/ambient_threats/arcs/trigger/ambient_threats/ambient_threats_arc.json" :
             "file(data/gm/campaigns/ambient_threats/arcs/trigger/ambient_threats/ambient_threats_arc.json)",
          "stonehearth/data/gm/campaigns/ambient_threats/arcs/trigger/ambient_threats/encounters/randomize_daily_threat.json" :
             "file(data/gm/campaigns/ambient_threats/arcs/trigger/ambient_threats/encounters/randomize_daily_threat.json)"
   }
}

The notable parts here are the first value in "aliases" and the values in "mixintos" though you guys should know by now what happens here. :wink:

Let’s end this off with a screenshot of this example mod in action.


You can do it ms. footman!



And that’s it for this section, hope it helped you modders in adding new and frightening threats!

3 Likes

i just looked at this the other day and was wondering when you were going to update it :smile:

i definitely look forward to reading this.

3 Likes