Crafting jobs

Let's create a new crafting job. We'll call it Lamplighter, they will craft fuel that will be used to light the lamps in the game (this example job comes from the Silent Woods mod).

Adding a crafting job

These are the steps to add a crafting 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 crafting jobs.

  1. We'll start by copying the herbalist job, for example (you can copy any other crafting job).

  2. We rename the files replacing herbalist with lamplighter.

  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:lamplighter": "file(jobs/lamplighter/lamplighter_description.json)"
    }
    
  4. We add the alias of the controller to the "controllers" section of our manifest:

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

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

    Our index.json file looks like this:

    {
        "jobs": {
            "silent_woods:jobs:lamplighter": {
                "description": "silent_woods:jobs:lamplighter"
            }
        }
    }
    

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

  6. We edit the talisman files (lamplighter_staff_talisman.json, lamplighter_staff.json, lamplighter_staff_iconic.json, and all the models and images).

    And we add aliases for both the talisman and the tool (weapon):

    "lamplighter:staff": "file(jobs/lamplighter/lamplighter_staff)",
    "lamplighter:talisman": "file(jobs/lamplighter/lamplighter_staff/lamplighter_staff_talisman.json)"
    
  7. Now we edit the lamplighter_description.json file. There are many fields that are common for any type of job. Here are the ones that are new for crafting jobs and how to set up some of the common ones:

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

      {
         "type": "entity",
         "components": {
            "stonehearth:equipment_piece": {
               "injected_ai": {
                  "ai_packs": [
                     "stonehearth:ai_pack:crafting",
                     "stonehearth:ai_pack:ladders",
                     "stonehearth:ai_pack:place_item",
                     "stonehearth:ai_pack:town_alert",
                     "stonehearth:ai_pack:wimpy",
                     "stonehearth:ai_pack:panic",
                     "stonehearth:ai_pack:panic:flee",
                     "stonehearth:ai_pack:panic:cower",
                     "silent_woods:ai_pack:refueling"
                  ]
               },
               "injected_commands": []
            }
         }
      }
      

      Notice that we inject the crafting AI pack so that the lamplighter knows how to craft, the town_alert so that they can rally, and some other basic AI packs that allow them to place ladders, items, and run away. We also added a custom AI pack defined in our mod (notice that the namespace is different).

      If you look at other examples from the stonehearth mod, you'll see that we can add any other AI pack here, for instance the Cook has the farming AI pack, the Herbalist has the healing AI pack, and the Engineer has the mining AI pack.

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

      "task_groups": [
        "stonehearth:task_groups:common_tasks",
        "stonehearth:task_groups:restock",
        "stonehearth:task_groups:placement",
        "stonehearth:task_groups:build",
        "stonehearth:task_groups:rescue",
        "stonehearth:task_groups:crafting",
        "stonehearth:task_groups:town_alert"
      ]
      

      We make sure that it includes the crafting task group, besides the usual task groups. In our case we also included the build task group but not the mining task group so the Lamplighter will be able to build but not to mine.

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

      "xp_rewards": {
         "craft_level_0": 15,
         "craft_level_1": 17,
         "craft_level_2": 19,
         "craft_level_3": 21,
         "craft_level_4": 23,
         "craft_level_5": 25,
         "craft_level_6": 27
      }
      

      The crafter will receive those many experience points depending on the level requirement of the recipe. The amount will vary also depending on the Curiosity attribute, and the difference between the crafter level and the recipe level (check the crafter component).

      The amount of experience required to level up increases with each level, so we usually set the experience rewards incrementally. The craft_level_0 is there because in the past, the levels for jobs started from 0. The existing jobs might still use it. If you set up all of your recipes to have a level requirement of 1 or higher, you might not need it for your custom crafter.

    • "level_data": {} -- most of the stonehearth crafters have the same perks, so you can reuse those if you want to (unlocking recipes or the ability to craft items of fine/excellent quality).

      Recipes will be automatically unlocked as the crafter reaches their level requirement, so only add that kind of perk if your crafter has recipes with a level requirement of that level. Fine/excellent quality will only be possible if the produced item has

      "stonehearth:item_quality": {
         "variable_quality": true
      }
      

      in their entity_data. So only add those perks if your produced items have variable quality. For all crafters, they'll be able to start crafting fine items at level 3 (no matter the level of the recipe), and excellent items at level 5.

    • "crafter": {} -- this field is exclusive for jobs with crafting abilities. Here we have two properties:

      • "work_effect" -- name of the effect file (only the name, don't end it by .json!) that will be played by the crafter when they are working. Normally includes an animation track, and maybe a sound track. For the Lamplighter, we'll use "fiddle".
      • "recipe_list" -- path to the recipe list JSON file. For our Lamplighter example it will be "/silent_woods/jobs/lamplighter/recipes/recipes.json".

      The recipes list is organized by groups of recipes and identifiers for recipe files. You can check how to set it up here.

    • "workshop": {} -- properties for the workshop:

      • "portrait" -- a path to the image of the basic workbench. Likely not in use nowadays.

      • "workbench_type" -- URI of the path to the basic workbench. Likely not in use nowadays.

      • "skin_class" -- an identifier for the CSS (Less) class for the workshop. In our case, "lamplighter". We have to declare the .less file of our workshop in the "ui" section of our manifest like this:

          "ui":{
             "less":[
                "file(jobs/lamplighter/skin/workshop.less)"
             ]
          }
        

        It will define the skin for our crafter's workshop in the UI. Our workshop.less file looks something like this:

          #craftWindow.lamplighter {
             background: url('skin.png');
        
             .item {
                background-image: url('skin.png');
             }
        
             #craftButton {
                position: absolute;
                right: 10px;
                top: -50px;
                width: 90px;
                height: 151px;
                #craftButtonImage {
                   background: url('saw.png');
                }
             }
        
             #orderListContainer {
                table{
                   background: #1f1e1e;
                }
        
                .orderListRow {
                   background: #1f1e1e;
                }
             }
          }
        

        Here we can nest CSS properties and change the appearance of the workshop UI with a custom background (skin.png in the example above) and a custom craft button (saw.png in the example above). We can edit the images in an illustration program that supports transparency. Note the lamplighter class being used in the main element (#craftWindow.lamplighter).

      • "open_sound" and "close_sound" -- aliases of sounds to play when we open and close the workshop UI that contains the list of recipes. We normally reuse sounds from the stonehearth mod, such as "stonehearth:sounds:ui:carpenter_menu:menu_open" and "stonehearth:sounds:ui:carpenter_menu:menu_closed".

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

    • Files related to the workbench. Notice that the workbench.json file has the workshop component pointing to the job alias:

      "stonehearth:workshop": {
         "job_alias": "silent_woods:jobs:lamplighter"
      }
      

      This is used to recognize the item as a crafter workbench, and will automatically add a command to it for the unit frame so that we can open the workshop UI for that crafter from it.

      In its entity_data, you might notice the following section:

      "stonehearth:table": {
         "drop_effect": "carry_putdown_on_table",
         "drop_offset": { "x": 0, "y": 1.25, "z": 0 }
      }
      

      These properties are used to define the effect that the crafter will play to drop the ingredients on top of the workbench, and an offset to positionate the ingredients once they're dropped and the produced items before the crafter picks them up.

      icon We can define workbench equivalents in the entity_data of the workbench.json file too. For example:

      "stonehearth:workshop": {
         "equivalents": [ "stonehearth:mason:pedestal:amberstone" ]
      }
      

      This will make it so that recipes that require the Mason's pedestal can be crafted using the Amberstone pedestal instead. Another example would be the upgraded Potter's kiln and workbench. Make sure to use the correct namespace when declaring them.

      In the entity_data of the ghost file for the workbench you might see this field:

      "stonehearth:hide_child_entities_from_pathfinder": true
      

      This is a flag so that hearthlings don't consider the ingredients that have been dropped on top of the workbench when they're trying to find items of that type for other AI actions.

      The iconic file for the workbench usually has the same default effect than the talismans.

    • We edit the files for the crafter outfit, which are the same than for any other type of armor though usually they don't include armor_data.

    • 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. Crafting jobs inherit from crafting_job.lua instead, because that file already inherits from base_job.lua. The CraftingJob class defined in crafting_job.lua takes care of adding the crafter component to the hearthling and managing the experience gain for crafters when they craft a recipe.

      Our lamplighter.lua file would look like this after we rename Herbalist to Lamplighter and remove all the unneeded code:

      local CraftingJob = require 'stonehearth.jobs.crafting_job'
      
      local LamplighterClass = class()
      radiant.mixin(LamplighterClass, CraftingJob)
      
      return LamplighterClass
      

      Notice how we mixin the CraftingJob class to our LamplighterClass, to retrieve all the common code for crafters. The example above is the minimal code that a crafting class should have. In this Lua file we can add more custom functions if we need to. For example, take a look at engineer.lua, herbalist.lua, or geomancer.lua.

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

      -- Overriding standard crafting class functions looks like this
      function LamplighterClass:promote(json_path, options)
         -- First call the crafting class's version
         CraftingJob.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 our crafting job to the list of workshops in the start menu. We need to add this mixinto:

    "mixintos" : {
       "stonehearth/data/ui/start_menu.json" : "file(data/ui/start_menu.json)"
    }
    

    The mixinto file will look like this:

    {
       "crafter_menu": {
          "items": {
             "lamplighter": {
                "name": "i18n(silent_woods:ui.game.menu.crafter_menu.items.lamplighter.display_name)",
                "description": "i18n(silent_woods:ui.game.menu.crafter_menu.items.lamplighter.description)",
                "class": "button",
                "required_job": "silent_woods:jobs:lamplighter",
                "required_job_text": "i18n(silent_woods:ui.game.menu.crafter_menu.items.lamplighter.required_job_text)",
                "icon": "/silent_woods/jobs/lamplighter/images/lamplighter_menu_icon.png",
                "menu_action": "show_crafter_ui",
                "hotkey_action": "craft:lamplighter"
             }
          }
       }
    }
    

    We add a key for our new job, a localized "name" and "description" for the tooltip, a CSS "class" (normally the same than the one that we copied), a "required_job" which is the alias of our crafter preceded by our mod's namespace plus a colon, a localized "required_job_text" which will be displayed in the tooltip when the player hasn't promoted anyone to our crafting job yet, a path to an "icon" to display for the menu item, "menu_action" : "show_crafter_ui" (which will make it so that clicking in the button will open our crafter's workshop), and "hotkey_action" which will point to a hotkey from hotkeys.json so that players can bind keys to open our crafter's workshop with them.

    Mind that if we add a hotkey action here we'll have to add a mixinto to stonehearth/data/hotkeys.json and add the action there too.

    icon Make sure to create a recipe for another crafter for your talisman to be obtainable in the game, or make it obtainable in some other way.

  10. Finally we can test our new crafting 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 another 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 silent_woods:jobs:lamplighter
    

    And then check if the workshop UI looks fine and the recipes can be crafted, etc.

Using SHED

Disclaimer: These steps refer to creating a crafting 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:carpenter').

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

  • Click on clone and review the dependencies. 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:carpenter' node. Clone it replacing carpenter 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.

  • 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.
    • Edit the paths to the images of the perks so that they're correct. For crafting jobs, if you're going to reuse the icons from the stonehearth mod you can rewrite them as absolute paths (remove the last parenthesis and edit the first part of the path to be /stonehearth/jobs/common).
    • Save the changes.
  • You'll have noticed that the recipes index hasn't been cloned. We have to clone it separately like we did for the controller alias.

    • Search for the 'recipes' node under the 'jobs:carpenter' node of the stonehearth mod, right-click on it, and click on 'Clone'.

      icon For easier cloning, you can try with the engineer recipe list, which has less items and will be easier to edit.

    • In the clone dialog, set the Target mod to your mod.

    • Set the replacement text for 'recipes' to 'recipes' and add a new replacement text with the + button, to replace carpenter with the name of your job (or engineer instead of carpenter if you cloned that one). This is so that the path of the cloned recipes list matches the path of our new job.

    • Now click on 'Clone!'. In the preview window, click on 'Uncheck All', since it's trying to clone all the produced items and we don't want that.

    • Check only the path for the recipes.json file (make sure it points to the folder where your cloned job is) and if you want to, clone one recipe JSON file too (for example, the one for the workbench, but don't clone its alias since we already have that from some steps above). Click on 'Accept'.

  • After SHED refreshes, go back to your cloned job file and edit the path of the recipes list so that it points to your mod. SHED might hang for a couple seconds when editing file paths, but hopefully it shouldn't be too bad. Now you must go to File -> Reload so that the recipe list gets detected in the tree view. Be patient.

  • Now you can click on your cloned recipes node, delete the contents under all the "recipes" entries (you can leave the categories if you want to reuse them or add your custom ones). If you cloned the recipe of the workbench a couple steps above, you should see it highlighted in green now, and recognized by SHED in the tree view.

  • Finally, keep editing the models, data, 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). The UI files for the workshop will have to be added to the manifest outside of SHED, like all the mixintos.