[Tutorial] Adding custom creatures

I’ve added a new entry to the golem’s description file ("player_id") and, of course, described how it’s used.

It’s not really a bad idea, though there are some cons with that. When it’s written down like this the modder can go at this at their own pace, while with a video they have to pause and retrace if something wasn’t all too clear at first.

Definitely not dissing the idea though, but we’ll see what happens. :smile:

well if you want, I can try and make some time to do the editing for you. My JSON is too outdated to actually follow your written tutorial and record it with your help… I’d need to catch up on it first :stuck_out_tongue:

This modding is making my head explode :tired_face:

Sooo… I did follow this guide and this is showing up:

> -- Script Error (lua) Begin ------------------------------- 
> 2015-Jul-08 17:53:38.566044 | client |  0 |                         lua.code |    std::exception: 'invalid file path '/additionalpets/entities/additional_pets/little_dragon'.'
> 2015-Jul-08 17:53:38.566044 | client |  0 |                         lua.code |    stack traceback:
> 2015-Jul-08 17:53:38.566044 | client |  0 |                         lua.code |    	[C]: ?
> 2015-Jul-08 17:53:38.566044 | client |  0 |                         lua.code |    	[C]: in function 'create_authoring_entity'
> 2015-Jul-08 17:53:38.566044 | client |  0 |                         lua.code |    	radiant/modules/client_entities.luac:3: in function 'create_entity'
> 2015-Jul-08 17:53:38.566044 | client |  0 |                         lua.code |    	debugtools/call_handlers/debugtools_commands.lua:15: in function 

How is this pad invalid? Before it gave me a could not find manifest.json error and now this

Full path:

D:\Program Files (x86)\Steam\SteamApps\common\Stonehearth\mods\additionalpets\entities\additional_pets\little_dragon

Full manifest.json

{
    "info": {
        "name": "additionalpets",
        "description": "",
        "version": 1
    },
    "aliases": {
        
	  
       "pets:littledragon": "file(entities/additional_pets/little_dragon)",
       "skeletons:littledragon": "file(data/rigs/little_dragon)"
        }
}

There are no typos on your folders or on the actual files?

Would you mind if you uploaded your mod for me to take a look?

1 Like

I can already see the type, in the first picture it says "/additionalpets/entities/additional_pets/little_dragon’."
In the second one the path is \additionalpets\entities\additional_pets\little_dragon

I believe you need to use the forward slas like this “/”

1 Like

An update!

I’ve rewritten how the AI is added to entities since that’s been completely changed in Alpha 11.
Also I’ve added which mixins you can use for your creature (it’s detailed right under the breakdown of the golem’s description).

@Feashrind, we’ve managed to solve the issue; it was a typo in the file names, they didn’t have the same name as the folders they were contained in and therefore “file(entities/additional_pets/little_dragon)” couldn’t find the file.

2 Likes

2.1.1 - Scenario Schmenario


Let’s start off with how to add through scenarios. So far the only creatures that come from scenarios are wild animals, so that’s also what this section will focus on; both friendly and hostile.

For this part of the turotial, we’ll be using the Mandragora for our examples. It’s not quite an animal, but good enough to be a wild creature. We’ll also be creating a new mod: scenario_ex.


First, we’ll take a look at the overview of the folders and files that we have:

You might notice a few differences from what we had before; we’ve got a new folder: scenarios. Within it we find a couple of files, which will be used to add the mandragoras into the scenario system.


Let’s look at the first one of these: the mandragora_nest.json file.

{
   "mixins"        : "stonehearth/scenarios/static/critter_nest/critter_nest.json",
   "name"          : "mandragora_nest",
   "habitat_types" : [ "mountains", "foothills" ],
   "weight"        : 3,
   "unique"        : false,
   "size":
   {
      "width"  : 16,
      "length" : 16
   },
   "data":
   {
      "critter_type" : [ "scenario_ex:mandragora" ],
      "num_critters":
      {
         "min" : 1,
         "max" : 2
      }
   }
}

As per usual; we’ll be looking at each of these in detail now. Only a few of these are necessary to use, but you can use it all if you wish; if you don’t then default values will be used instead.

  • "mixins" - Part 1 already covers how mixins work. The value shown here is what you want to use if you’re adding critters or other wild creatures.
  • "name" - Just the name of this scenario. Default is critter_nest.
  • "habitat_types" - On what type of area(s) you’d want this creature to appear on. The types you can choose at the moment are: mountains, foothills, plains, and forest. Defaults are plains and foothills.
  • "weight" - How big of a chance this scenario has to trigger; the higher the value the bigger the chance. Default is 10.
  • "unique" - If this scenario should only occur once. Default is false.
  • "size" - How big of an area the creatures can spawn in. Default is 16 by 16.
  • "data" - Some specific data for this kind of scenario.
    • "critter_type" - What creature(s) you want this scenario to spawn.
    • "num_critters" - Randomly choose how many critters there should be; from min through max. Default is 1 through 3.

Next we’ll be looking at scenario_index.json.

{
   "static":
   {
      "scenarios":
      [
         "file(static/mandragora_nest)"
      ]
   }
}

This one’s short and sweet! It will be used to say that we have our own scenario to add to their list of scenarios, which we’ll do through the manifest.
Speaking of the manifest; let’s look at it now.

{
   "info":
   {
      "name"    : "Scenario Example",
      "version" : 1
   },
   "aliases":
   {
      "mandragora"           : "file(entities/mandragora)",
      "skeletons:mandragora" : "file(data/rigs/mandragora)",
   },
   "mixintos":
   {
      "stonehearth/scenarios/scenario_index.json" : "file(scenarios/scenario_index.json)"
   }
}

Notice that we’re now using “mixintos.” If you’re unfamiliar with it, essentially what it does is to take a data file and combine it with another. Mixintos only work with json files. If you want to know more about it, and about overrides, you can take a peek over here.

And that is it! That’s all the extra work to populate the world of Stonehearth with your own critters and/or wild creatures.

Here are the mandragoras in all their glory!


### 2.1.2 - Hostile lands, but with great loot

How about we stray away (for a bit) from adding creatures into the world, and get into how to make them get their own, specific, weapons and loot drops, and how to make them hostile against the player of course.

For the mandragora; we’re adding a new weapon for it to use and a couple of new items for it to drop. Here’s how we’ve set up the new files.


For this we need to add some values to the mandragora’s description, here’s the new and improved mandragora:

{
   "type"        : "entity",
   "mixins"      : "stonehearth:mixins:monster",
   "init_script" : "file(mandragora_init_script.lua)",
   "components":
   {
      "render_info":
      {
         "animation_table" : "scenario_ex:skeletons:mandragora",
         "scale"           : 0.1
      },
      "model_variants":
      {
         "default":
         {
            "models" : [ "file(mandragora.qb)" ]
         }
      },
      "unit_info":
      {
         "name"        : "A Mandragora",
         "description" : "A rare, dangerous, and mind-numbingly loud plant",
         "player_id"   : "undead"
      },
      "stonehearth:material":
      {
         "tags" : "plant"
      },
      "stonehearth:equipment" : {},
      "stonehearth:attributes":
      {
         "max_health":
         {
            "type"  : "basic",
            "value" : 70
         },
         "health":
         {
            "type"     : "variable",
            "equation" : "max_health"
         },
         "speed":
         {
            "type"  : "basic",
            "value" : 35
         },
         "menace":
         {
            "type"  : "basic",
            "value" : 30
         }
      }
   },
   "entity_data":
   {
      "stonehearth:entity_radius" : 0.75,
      "stonehearth:entity_reach"  : 1.0,
      "stonehearth:observers:avoid_threatening_entities":
      {
         "min_avoidance_distance" : 16,
         "max_avoidance_distance" : 16
      },
      "stonehearth:destroyed_loot_table":
      {
         "num_rolls":
         {
            "min" : 1,
            "max" : 1
         },
         "items":
         [
            { "uri" : "scenario_ex:mandrake:leaves", "weight" : 4 },
            { "uri" : "scenario_ex:mandrake:root",   "weight" : 2 }
         ]
      }
   }
}

There are some differences here from what we’ve seen of the golem before; let’s focus on those.
First off is "init_script," with a lua file as its value; we’ll take a look at that file soon.
Next up is that there’s the mandragora has undead as its player_id. This is a bit of a hack to make them hostile against the player, of course they’re not undead but this is the only population which is always hostile against anything. So it’s an easy way to make a creature into your enemy.
And then under entity_data there is "stonehearth:destroyed_loot_table," this gives the creature loot to drop on death. You can specify how much loot you want the creature to drop through "num_rolls," and in "items" you say what items should be dropped; where each item has a “uri” and a “weight.”


On to the script file!

local init_fn = function(entity)
   entity:add_component('stonehearth:equipment')
            :equip_item('scenario_ex:mandragora:voice')
end

return init_fn

It’s not too much you need to worry about here. You can easily copy this text into your own init script file, just be sure to change 'scenario_ex:mandragora:voice' into whatever weapon you’d want your creature to wield.


Let’s show off how the weapon’s json file looks like:

{
   "type" : "entity",
   "components":
   {
      "stonehearth:equipment_piece":
      {
         "slot"   : "mainhand",
         "ilevel" : 0,
         "roles"  : "combat"
      }
   },
   "entity_data":
   {
      "stonehearth:combat:weapon_data":
      {
         "base_damage" : 13,
         "reach"       : 1.0
      },
      "stonehearth:combat:armor_data":
      {
         "base_damage_reduction" : 0
      },
      "stonehearth:combat:melee_attacks":
      [
         {
            "name"         : "combat_scream",
            "active_frame" : 17,
            "cooldown"     : 2000,
            "priority"     : 1
         }
      ],
      "stonehearth:combat:melee_defenses":
      [
         {
            "name"         : "combat_dodge",
            "active_frame" : 8,
            "cooldown"     : 10000,
            "priority"     : 1
         }
      ]
   }
}

Its type is entity, as it must be. It’s got only one component: the equipment_piece component. It basically what’s needed for this entity to become wielded as an equipment by other entities.
Other interesting points here are those under “entity_data.”

  • "stonehearth:combat:weapon_data" - Here we specify how much damage the weapon inflicts and how much range it has from its user.
  • "stonehearth:combat:armor_data" - How much armor it grants to its user.
  • "stonehearth:combat:melee_attacks" - Here’s a list of all the attacks that can be done by this equipment. What we want to note here is that there must be an effect with the name shown here, which will be playing the attack animation with sounds and such. "active_frame" tells us which frame the hit occurs in.
  • "stonehearth:combat:melee_defenses" - Almost like the one above, only here it’s used to defend against incoming blows.

Note that this file holds data specific for only the mandragora; it’s not intended for anything else. If you’re looking to add a weapon that you’re hearthlings can also use; take one of Stonehearth’s weapons (located in stonehearth/entities/weapons) and change some values to reflect your weapon.


We’ll also have to update the manifest with three more entries to aliases:

   "aliases":
   {
      "mandragora"           : "file(entities/mandragora)",
      "skeletons:mandragora" : "file(data/rigs/mandragora)",
      "mandragora:voice"     : "file(entities/mandragora/mandragora_voice.json)",
      "mandrake:leaves"      : "file(entities/mandrake_leaves)",
      "mandrake:root"        : "file(entities/mandrake_root)"
   },

And with that, you can add your creatures in-game, give them some weapons to fight with, some loot to drop, and some extra fun for the player.

4 Likes

Oh god this is so helpfull :smiley: i was looking for something like this for my mod (wich hasnt progressed in a long time.

2 Likes

2.2 - Critters all around!


So now we’re going to look at how to make new critters to be captured by the trappers. First of all though; I’d like to give thanks to @RepeatPan with his solution, without that this section would not exist!


We’ll be using the mandragora once again for this. Though now we’re stripping away a lot of components and other unnecessary stuff, and only focus on making it into a pure critter. So let’s look at its new description file.

{
   "type"   : "entity",
   "mixins" : "stonehearth:mixins:critter",
   "components":
   {
      "render_info":
      {
         "animation_table" : "critter_ex:skeletons:mandragora",
         "scale"           : 0.1
      },
      "model_variants":
      {
         "default":
         {
            "models" : [ "file(mandragora.qb)" ]
         }
      },
      "unit_info":
      {
         "name"        : "A Mandragora",
         "description" : "A rare, dangerous, and mind-numbingly loud plant",
         "player_id"   : "critters"
      }
   },
   "entity_data":
   {
      "stonehearth:harvest_beast_loot_table":
      {
         "num_rolls":
         {
            "min" : 1,
            "max" : 1
         },
         "items":
         [
            { "uri" : "critter_ex:mandrake:leaves", "weight" : 4 },
            { "uri" : "critter_ex:mandrake:root",   "weight" : 2 }
         ]
      }
   }
}

Notice that we’re using critter as a mixin here; it holds a lot of data specific for all critters, and because of that we don’t have to add that much more to this except for data specific for this critter (of course).

Looking at "entity_data," we can see that it’s got its loot table just as decribed in 2.1.2. The difference here is that we’re using "stonehearth:harvest_beast_loot_table" instead of "stonehearth:destroyed_loot_table." There are a few differences between them: "destroyed_loot_table" is what the entity will drop when it’s destroyed at any time now matter the circumstance, and the loot will always belong to the faction that the entity belonged to (so the player will have to use the loot command to take the items), while "stonehearth:harvest_beast_loot_table" is specific for when the entity is ‘harvested’ through the trapper, which means that the loot will only drop by such an event and it will always belong to the faction which ‘harvested’ the poor creature (removing the need of the loot command).


Right, now the critter is ready to go; we just need to have it become a magnet for traps. For that we have to look at some Lua code, and add something new to the manifest. Let’s look at the manifest before getting into the nitty-gritty.

{
   "info":
   {
      "name"    : "Critter Example",
      "version" : 1
   },
   "server_init_script" : "file(scenario_server)",
   "aliases":
   {
      "mandragora"           : "file(entities/mandragora)",
      "skeletons:mandragora" : "file(data/rigs/mandragora)",
      "mandrake:leaves"      : "file(entities/mandrake_leaves)",
      "mandrake:root"        : "file(entities/mandrake_root)"
   }
}

I bet you’ve noticed the new entry here: "server_init_script." Basically, this points to a script file which is run, on the server side, at the game’s start-up. There is also another one ("client_init_script") for the client side, but it’s not needed here so we’re ignoring that. One thing I’d like to point out is that the file extension must be excluded, or else it won’t run (for some reason).


Now we can take a look at the Lua file.

local rng = _radiant.csg.get_default_rng()
local critter_ex = class()

function critter_ex:_init()
   local component = radiant.mods.require('stonehearth.components.trapping.trapping_grounds_component')

   local old_initialize = component.initialize
   function component:initialize(...)
      local ret = { old_initialize(self, ...) }
      self._spawnable_critters = self._spawnable_critters or { 'stonehearth:rabbit', 'stonehearth:racoon', 'stonehearth:red_fox', 'stonehearth:squirrel' }
      -- This is where we add our own critter. If you have multiple critters you'd like to add; copy the below line for each critter.
      table.insert(self._spawnable_critters, 'critter_ex:mandragora')
      return unpack(ret)
   end

   function component:_choose_spawned_critter_type()
      return self._spawnable_critters[rng:get_int(1, #self._spawnable_critters)]
   end
end

radiant.events.listen_once(radiant, 'radiant:required_loaded', critter_ex, critter_ex._init)

return critter_ex()

Again, this is from @RepeatPan’s solution, so all credit goes to him.

This tutorial won’t go into detail exactly how this works. Let’s just say that we’re getting the trapping grounds component, and changing a couple of functions in it into our own implementation which allows us to add new critters.
With this kind of implementation, even if several mods were to add their own critter that can be captured, it will work for all of them, which makes this solution very powerful in that regard. But that’s assuming that all of the mods are using exactly this solution.
If you’re using this implementation for yourself; be sure to change 'critter_ex:mandragora' to your own critter, and also change the 'critter_ex' variable to the name of your mod.

To close this up; let’s take a look at some poor critters in cages (because why not).

The mandragora is not pleased.

A wild, and tiny, golem. It looks confused as to how it got there.

4 Likes

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