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:

  • "damage_multiplier" -- with this property, when the attack is executed the total damage is increased or decreased based on this multiplier.

  • "aggro_addition" -- we can make this attack increase the aggro towards our combat unit.

  • "aggro_multiplier" -- like the above property, but using a multiplier instead of adding some amount. If both are present, the multiplier will be applied before the addition.

  • "minimum_damage" -- we can limit the minimum damage that this attack should make. If the damage is negative, then it will be changed to either 0 or 1.

  • "inflictable_debuffs" : {} -- same syntax than for other inflictable debuffs. We can make this attack inflict a debuff on the target with a chance.

    "inflictable_debuffs": {
       "infection": {
          "uri": "stonehearth:buffs:knight:taunted_effect",
          "chance": 1.0
       }
    }
    
  • "aoe_effect" : {} -- this attack will cause aoe damage. It includes at least an "aoe_range" property (the range in world units where the aoe damage will have effect, by default it will be the melee attack's range) and an "aoe_target_limit" property (the maximum number of targets that can be affected by the aoe, 10 by default). It can also include any of the fields described above, so that the aoe attack can increase aggro, inflict debuffs on multiple targets, etc.

For ranged attacks:

  • Besides the normal "active_frame" property, we can have an "active_frames" : [] array, to specify more than one frame. It's used for the archer double shot skill. We could also use this property for melee attacks.
  • "target_armor_multiplier" -- normally a value between 0 and 1, this will reduce the armor for the target, so the damage will be higher. Also possible to use in melee attacks.

For melee defenses:

  • "chance_of_success" -- normally a value between 0 and 1 (1 by default). It is the chance to block attacks.
  • "defense_attribute" -- the name of an attribute that we can use to increase the chance of success, 0 by default. We can use any attribute in this field, but for it to have effect the hearthlings must already have it in their attributes component.

Using SHED

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

  • First we'll need to clone a job node from the stonehearth mod (for example, 'jobs:footman').

  • Right-click on it and click on 'Clone!'. Select your mod as the Target mod for the clone, and type the name of your combat job in the replacement text box.

  • Click on clone and review the dependencies. Uncheck any unwanted files from buffs and the like if you won't use them, and go back if you want to add more replacement texts for the names of the rest of the files, so that they're cloned already with the name that you want.

    Make sure to uncheck the alias for the controller (your_mod:class:your_job) since it would be created inside the aliases node, which is something that we don't want. Click on 'Accept'.

  • Once SHED refreshes the view, look for the controllers node of the stonehearth mod and find (for example) the 'class:footman' node. Clone it replacing footman with the name of your custom job, but uncheck the Lua file, since we already cloned it in the previous step.

    This will make it so that the alias of the controller is cloned into the "controllers" section of your manifest, which is what we want.

  • Now search for the 'weapons:wooden_sword' node on the stonehearth mod and clone it to your mod, so that your talisman JSON file detects the missing models and images.

  • Select the node of your cloned job and start editing the files:

    • Edit the "alias", "controller", "talisman_uri" and any other fields with aliases so that they point to your mod's manifest. If SHED detects the file that they point to, it will highlight them in light green.

    • If your job perks include buffs, edit their aliases so that they point to the stonehearth mod (if you plan on reusing them) or to your mod (if you've cloned them). Same for the combat skills.

      Remember that the name of the effects is based on the stonehearth mod. If you plan to add custom animations, make sure to set the overrides properly so that the hearthlings detect them.

    • Save the changes.

  • Finally, keep editing the models, data of the perks, controller, etc. like mentioned in the guide above until your cloned job is ready for testing. You can use the 'Localize This File' button to try auto-localizing your JSON files (mind that for some files it might not work).

    Remember to add your job to the job index via a mixinto outside of SHED, otherwise you won't be able to test it. Also make a recipe for the talisman or make it obtainable in some other way.