[Tutorial] Adding custom creatures

Woohh! that was fast! Alot of modders were waiting for this :smiley:

I will update my progress.

3 Likes

Now do a Blender animation tutorial. xD

1 Like

I would say “Contains the origin position of each object in the model. The origin of an object is the point around which that object animates.”

2 Likes

What do you want to learn?

Thanks, I’ve updated it accordingly.

Haha yeeeeaaah, I’m a coder not an animator. :stuck_out_tongue:

You should take a look over at voxel_pirate’s stuff for those kinds of tutorials.
Here’s an overview:

3 Likes

you should most definately pour that into a youtube video, it just adds a visual part making it easier for ppl to learn since they watch someone do it!

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.