Combat jobs

Combat is controlled using many files: the combat service, combat_state / target_tables components, AI actions under stonehearth/ai/actions/combat, etc.

Let's create a new combat job. We'll call it Crusader, they will be an advanced class promoted from Footman.

Adding a combat job

These are the steps to add a combat job manually. We'll quickly go over them since the common parts have already been explained in another page, and explain the features specific to combat jobs.

  1. We'll start by copying the footman job, for example (you can copy any other combat job, you'll likely want to copy the archer if you want to make a ranged unit).

  2. We rename the files replacing footman with crusader.

  3. We add an alias for our job in our mod's manifest, as well as an alias for our job index, to use it for the mixinto:

    "aliases": {
       "jobs:index": "file(jobs/index.json)",
       "jobs:crusader": "file(jobs/crusader/crusader_description.json)"
    }
    
  4. We add the alias of the controller to the "controllers" section of our manifest:

    "controllers":{
       "class:crusader": "file(jobs/crusader/crusader.lua)"
    }
    
  5. We add a mixinto to the jobs index to add our Crusader job to the list of existing jobs:

    "mixintos": {
       "stonehearth:jobs:index": "crusader:jobs:index"
    }
    

    Our index.json file looks like this:

    {
        "jobs": {
            "crusader:jobs:crusader": {
                "description": "crusader:jobs:crusader"
            }
        }
    }
    

    The key of the job happens to match with the alias, which is what the "description" field points to.

  6. Since the talisman corresponds to an actual weapon, we have created it under my_mod/entities/weapons. We edit its related files (battle_hammer_talisman.json, battle_hammer.json, battle_hammer_iconic.json, and all their models and images).

    We also add aliases for both the talisman and the weapon:

    "crusader:talisman": "file(entities/weapons/battle_hammer/battle_hammer_talisman.json)",
    "weapons:battle_hammer": "file(entities/weapons/battle_hammer)"
    
  7. Now we edit the crusader_description.json file. There are many fields that are common for any type of job. Here's how to set them up for combat jobs:

    • "abilities" -- for our example, the crusader_abilities.json file will look like this:

      {
         "type": "entity",
         "components": {
            "stonehearth:equipment_piece": {
               "injected_ai": {
                  "ai_packs": [
                     "stonehearth:ai_pack:patrolling",
                     "stonehearth:ai_pack:wimpy",
                     "stonehearth:ai_pack:panic:flee",
                     "stonehearth:ai_pack:combat_control"
                  ]
               },
               "injected_buffs": [
                  "stonehearth:buffs:combat_basics"
               ],
               "military_score": 20
            }
         },
         "entity_data": {
            "stonehearth:combat:melee_attacks": [
               {
                  "name": "combat_1h_forehand_spin",
                  "effect": "combat_1h_forehand_spin",
                  "active_frame": 21,
                  "cooldown": 8000,
                  "priority": 1
               }
            ],
            "stonehearth:combat:melee_defenses": [
               {
                  "name": "combat_1h_dodge",
                  "effect": "combat_1h_dodge",
                  "active_frame": 8,
                  "cooldown": 12000,
                  "priority": 1
               }
            ]
         }
      }
      

      Notice that we inject the patrolling AI pack so that the crusader knows how to patrol, a buff common for all combat units that increases their attributes, and an initial attack and defense skill (animation effects).

      The "military_score" property inside the equipment_piece component will make it so that the town's military score increases by the value defined there every time we promote someone to, in this case, Crusader. In the stonehearth mod only the advanced combat units have it. This way we can automatically increase / decrease the score by a higher amount every time we promote / demote a hearthling to a combat job.

      Military score (which you'll see in the town overview menu) is based on the weapons / tools / armor of the hearthlings, plus the combat units that we have (via these abilities files). Some aspects of the game will scale based on your military score.

      If you look at other examples from the stonehearth mod, you'll see that we can add any other AI pack here, or don't specify any attack (that's usually dependent on weapons themselves, so that the animations match with the weapon). The abilities file is like a default equipment piece or default skills that the job comes with.

    • "task_groups": [] -- for our example, this array will look like this:

      "task_groups": [
        "stonehearth:task_group:common_tasks",
        "stonehearth:task_group:restock",
        "stonehearth:task_groups:rescue"
      ]
      

      Here we have very few task groups since we want our unit to focus on combat. Notice how combat units don't have the town_alert AI pack / task group. In the case of the cleric, they have the healing task group here too.

    • "xp_rewards": {} -- for combat units, we set them up like this:

      "xp_rewards": {
         "town_protection": 1
      }
      

      This is the amount of experience that they will gain every now and then when protecting the town (either patrolling or standing in a defense zone). It is controlled by listening to an event in combat_job.lua. The amount of experience that they gain by killing enemies depends on the monster_tuning files (it will be scaled with difficulty, and each monster will reward a different amount of experience points).

      All party members receive the same experience while in combat, no matter who killed the enemies.

      The cleric also has a couple more settings in their "xp_rewards". In general, you can grant exp points via the add_exp function of the job component.

    • "level_data": {} -- combat units usually unlock permanent buffs and new combat skills.

      For example, the Crusader will have these two perks:

      "level_data": {
         "1": {
            "perks": [
               {
                  "type": "add_combat_action",
                  "name": "i18n(crusader:jobs.crusader.crusader_description.level_1_data.perk_000_name)",
                  "id": "crusader_holy_punch",
                  "icon": "file(images/holy_punch.png)",
                  "equipment": "crusader:crusader:holy_punch",
                  "action_type": "stonehearth:combat:melee_attacks",
                  "description": "i18n(crusader:jobs.crusader.crusader_description.level_1_data.perk_000_description)",
                  "level": 1,
                  "demote_fn": "remove_combat_action"
               }
            ]
         },
         "2": {
            "perks": [
               {
                  "type": "apply_buff",
                  "name": "i18n(crusader:jobs.crusader.crusader_description.level_2_data.perk_000_name)",
                  "id": "crusader_heal_aura",
                  "icon": "file(images/cleric_perk_healing_aura.png)",
                  "buff_name": "crusader:buffs:crusader:heal_aura",
                  "description": "i18n(crusader:jobs.crusader.crusader_description.level_2_data.perk_000_description)",
                  "level": 2,
                  "demote_fn": "remove_buff"
               }
            ]
         }
      }
      
      • The first one has "type" : "add_combat_action". This is the name of a function defined in combat_job.lua. Notice how we have the "demote_fn" field with the opposite function, "remove_combat_action".

        We declare an "action_type" so that the game knows where to inject the new combat skill (in this case, "stonehearth:combat:melee_attacks") and an "equipment" (in this case, "crusader:crusader:holy_punch") which is the URI of a JSON file describing an equipment piece, normally an invisible one that just adds a new combat skill (animation), or inflictable debuffs, etc.

        Remember that the damage is defined in the weapon, not in the attacks themselves, and that it will be modified by factors like attributes, buffs, or some properties of the attacks (that are added together with the animation info). For healing we have "stonehearth:combat:healing_spells", instead of melee attacks. It's used for the cleric, and doesn't have any special property besides the animation ones, because the heal amount depends on the entity_data of the weapon:

        "stonehearth:combat:healing_data": {
           "base_healing": 5
        }
        

        Normally we'll set the "priority" of the unlocked skills from the perks a bit higher, so that they have more chances to run than the basic attacks.

      • The second one has "type": "apply_buff" (with "demote_fn": "remove_buff"). These functions are defined in base_job.lua. We declare a "buff_name" property linking to the URI of the buff that we want to apply (normally a permanent buff).

      There are some more functions in base_job.lua and combat_job.lua that we can use for the perks.

  8. We would now proceed to edit the rest of files:

    • We edit the files for the default job outfit, which are the same than for any other type of armor, and normally include armor data:

      "stonehearth:combat:armor_data": {
         "base_damage_reduction": 7
      }
      
    • We edit the controller file. This is very important. Our renamed controller file will still have the same code inside so we have to rename some stuff in it.

      icon All jobs in the game inherit from base_job.lua. Combat jobs inherit from combat_job.lua instead, because that file already inherits from base_job.lua. The CombatJob class defined in combat_job.lua takes care of adding the combat unit to a default party when they get promoted, removing them when they are demoted to a non-combat job, and managing the experience gain for combat units when they patrol.

      Our crusader.lua file would look like this after we rename Footman to Crusader:

      local CrusaderClass = class()
      
      local CombatJob = radiant.mods.require 'stonehearth.jobs.combat_job'
      radiant.mixin(CrusaderClass, CombatJob)
      
      return CrusaderClass
      

      Notice how we mixin the CombatJob class to our CrusaderClass, to retrieve all the common code for combat units. The example above is the minimal code that a combat class should have. In this Lua file we can add more custom functions if we need to. For example, take a look at cleric.lua.

      In order to override inherited functions, we'd do this (after inheriting from the CombatJob class and before returning our CrusaderClass class):

      -- Overriding standard combat class functions looks like this
      function CrusaderClass:promote(json_path, options)
         -- First call the combat class's version
         CombatJob.promote(self, json_path, options)
         -- Your custom code goes here
      end
      

      This allows us to extend the functionality if we need to add extra variables or whatever new behavior to the class.

  9. Now we add a crafting recipe for the talisman to be obtainable in the game, or make it obtainable in some other way.

  10. Finally we can test our new combat job in the game and make sure that everything works as expected.

    You can either stamp the required talisman with the Item Stamper debugtool and promote the hearthlings via the promotion UI, or make a crafter craft the talisman if you made a recipe for it, etc.

    You can also promote them with the default console if you have the debugtools mod enabled, e.g.:

      promote_to crusader:jobs:crusader
    

    And then check if they get added to the parties menu, patrol, can fight, etc.

About attacks and defenses

We can easily add new combat skills with equipment pieces. Earlier we already explained the basic properties that they can have. But there are even more than just the info for the combat animation effect.

You can check the existing combat jobs, armor and weapons for examples.

For melee attacks:

For ranged attacks:

For melee defenses:

Using SHED

Disclaimer: These steps refer to creating a combat job using SHED at the time that this page was written.