Modding:Tutorial - Snapjaw Mages

From Caves of Qud Wiki
Jump to navigation Jump to search

Introduction

Welcome! If you're looking to learn how to write mods for Caves of Qud, this tutorial is a good place to start.

In this tutorial, we're going to add a new creature to the game -- the great and mystical snapjaw mage. Along the way we're going to be helped by my friends Rulimispruce, legendary sprouting orb and Pyovya, legendary mopango.

Sprouting orb.png < Howdy!
Mopango pilgrim.png < I'm excited!

By the end of this tutorial, you'll have created a mod for Caves of Qud which

  • introduces two new creatures, the snapjaw fire mage and the snapjaw ice mage, and
  • introduces two new missile weapons, the tome of fire and the tome of ice.

Before you get started, you may also want to check out the Blue Ctesiphus modding tutorial on Steam. It's a little dated, but it also covers many of the concepts covered here.

This mod won't require any scripting, so you don't need to know anything about C#. However, I'd recommend that you play through a good chunk of Caves of Qud before you try your hand at modding. It's helpful to be able to refer to existing creatures and items when creating new ones.

Source code

You can find the source for the final version of the mod here: https://github.com/TrashMonks/Snapjaw-Mages

In particular (especially if you're familiar with version control), try stepping through the repository's commit history; it follows the steps involved in this tutorial, and you can use it to see how the mod changes after every step. Otherwise, if you find that for one reason or another your mod isn't working at some point in the tutorial, take a look at the files in the repository (which contain a complete, working implementation of the mod you'll be creating in this tutorial).

Debugging issues in the tutorial

All of the code that appears in this tutorial should work as-is. However, if you are following along with the tutorial and creating your Snapjaw Mages mod manually, you may occasionally find that something isn't working as expected.

If you find yourself stuck, please take a look at #Appendix: Mod debugging, which addresses the most common issues that modders run into. Feel free to also ask for help in the #modding channel of the Caves of Qud Discord, or in the Kitfox Discord.

Getting started, and basic concepts

If this is your first time modding, make sure to follow the checklist in Modding:Overview. It's particularly important that you install a text editor like VS Code. I've prepared all of the tiles that you'll need to use for this tutorial, so you don't need to familiarize yourself with a pixel editor. However, if you plan on adding new tiles in the future you should be familiar with an editor like Piskel or Aseprite.

You should also enable the following options in your game:

  • Modding > Enable Mods
  • Debug > Show quickstart option during character generation.
    • This will cause a _Quickstart option to show up when you start a new game. This option makes it very easy to create a new character and immediately start testing out your mod.
  • (Optional) Debug > Show error popups
    • This option isn't strictly necessary but may make it easier to identify bugs when they pop up in your mods.

Finally, you should take a look at the page of file locations and find the following directories on your computer:

  • the "offline mods" folder; and
  • the "game data files" folder.

XML

XML (eXtensible Markup Language) is a type of markup language, which (loosely speaking) is a text-based format that can be understood by a program. Caves of Qud makes extensive use of XML for things like defining creatures, quests, items, conversations, and more. We'll be working with XML a lot throughout this tutorial, so it's helpful to have a little familiarity with XML first.

You can check out the Wikipedia page on XML if you want to learn more. Here's some basic terminology you should be familiar with: in the XML snippet below,

<foo bar="baz" />

foo is an XML tag. Meanwhile, bar is an attribute, which (in this case) has been assigned the value baz. The /> at the end tells us that we should close the XML (i.e. pair it with a matching closing tag). The following two lines of XML are equivalent:

<foo bar="baz"/>
<foo bar="baz"></foo>

You must always close your XML tags! A common source of issues for beginning modders is that they don't close their tags, which causes their XML files to be interpreted incorrectly by the game.

You may also see comments here and there. A comment consists of one or more lines of text between <!-- and -->, such as the following:

<!-- This is a comment! -->

<!--
  ... and this is also a comment!
-->

Comments don't do anything by themselves; they're just helpful for documenting things that are going on in your XML.

Wishes

Wishes are commands that can be executed in-game to perform a variety of tasks. They're very useful when modding -- you can use a wish to spawn a creature of your choice, or create an item, and a variety of other tasks. You should follow the instructions on the Wishes page to bind the wish command to a key combination.

There are a lot of useful wishes. Here are a few that I use often:

  • idkfa: puts your character in "God Mode". This makes your character immortal, and causes all attacks that penetrate to instantly kill.
  • swap: allows you to swap your body with an adjacent creature.
  • In addition, you can wish for the ID of a creature or item to spawn that creature/item. For instance, wishing for Troll King 1 will spawn Jotun, Who Parts Limbs.

Navigating the game's data files

We won't delve too deeply into Caves of Qud's data files, but it's helpful to know how to navigate through them when creating a mod. Make sure that you've found where your computer keeps the game's data files by looking at the list of file locations before continuing; it should be a subfolder called Base of a folder called StreamingAssets somewhere on your machine.

Mopango pilgrim.png < Don't modify the game's files! If you make a mistake, you could end up having to reinstall the game.

Once you've found this folder, you should see a bunch of files with names ending in things like .bmp, .rpm, .xml, .json, and so on.

Snapjaw Mages -- Game files.webp

For this tutorial, the .xml files are the ones we're going to be the most interested in. There are a lot of these files (35 as of game version 2.0.205.63); here's a high-level overview of just a few of them:

  • Bodies.xml: in charge of defining various body parts and creature anatomies. This is useful when you want to define a new body type; for instance, the Blahajar mod defines a unique anatomy specific to sharks.
  • Conversations.xml: defines the conversational script that different creatures have.
  • Mutations.xml: data file containing all of the mutations that are available in-game.
  • ObjectBlueprints/Creatures.xml: definitions of all of the creatures that exist in Caves of Qud.
  • ObjectBlueprints/Items.xml: definitions of all of the items available in Caves of Qud.
  • PopulationTables.xml: contains population tables, which are used to figure out when and where creatures should spawn.
  • Skills.xml: contains all of the skill trees provided by Caves of Qud, and abilities within those trees.
  • Quests.xml: lays out the steps and rewards for non-procedurally generated quests.

When adding new creatures and items (as in this tutorial!), we're mostly going to be interested in just ObjectBlueprints/Creatures.xml and ObjectBlueprints/Items.xml. Starting in the next section, we'll discuss what all of the bits and pieces mean in these files. Make sure you know where to find these files before you continue!

Creating a mod

Mopango pilgrim.png < Let's get started!

Our first goal is to create a mod that doesn't actually do anything. Start by finding Caves of Qud's "offline mods" folder; check the file locations page to see where this folder is on your operating system. Inside this folder (it should be named Mods), create a subfolder called Snapjaw-Mages. The first file we're going to write for our mod is a manifest file; this contains metadata such as our mod's title, a description of the mod, and its version. Create a file called manifest.json inside the Snapjaw-Mages/ folder, and paste in the following contents:

{
    "id": "Pyovya_SnapjawMage",
    "title": "Snapjaw Mages!",
    "description": "Adds the new Snapjaw Mage creature to Caves of Qud.",
    "version": "0.1.0",
    "author": "Pyovya",
    "tags": "Creature",
    "PreviewImage": "preview.png"
}
Mopango pilgrim.png < Feel free to replace Pyovya with your own name!

We should also get a preview image for the mod. I've made one that you can use here:

https://github.com/TrashMonks/Snapjaw-Mages/blob/main/Snapjaw-Mages/preview.png

Download this image and place it in the SnapjawMage/ folder, so that now your folder looks like this:

Snapjaw-Mages
├── manifest.json
└── preview.png

Now start up Caves of Qud. In the splash screen, click on "Installed Mod Configuration". If everything went correctly, you should see a "Snapjaw Mages!" mod appear in your list of installed mods:

Snapjaw Heros -- Mods List.webp

You will be returning to this screen often!

Mopango pilgrim.png < Every time you make a change to your mod, you will need to go back to this screen and activate the "Save and Reload" option (which you can do by pressing r). You should see a bar saying "reloading mod configuration" appear at the bottom of your screen, like so:

Snapjaw Mages -- reloading mod config.webp

Your first creature

Adding a new creature to Creatures.xml

In your mod directory, create a folder called ObjectBlueprints/, and within that folder open up a file in your text editor called Creatures.xml. Let's start by adding the following to Creatures.xml:

<?xml version="1.0" encoding="utf-8" ?>
<objects>
</objects>

Save this file! Before continuing, make sure your mod directory looks something like this:

Snapjaw-Mages
├── manifest.json
├── ObjectBlueprints
│   └── Creatures.xml
└── preview.png

With that out of the way, let's start adding our snapjaw mage to Creatures.xml:

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <object Name="Pyovya_SnapjawMage_Snapjaw Mage" Inherits="Snapjaw">
  </object>
</objects>

Let's break this down:

  • The object tag tells Qud that we want to add a new creature.
  • The Name attribute is a unique identifier for our new creature (it's not the name of the creature as it appears in-game -- we'll get to that shortly). You'll use this identifier whenever you want to wish for a snapjaw mage.
  • The Inherits attribute says that our new creature should "inherit" all of the properties of the Snapjaw creature.
Sprouting orb.png < Hey, why'd you add the Pyovya_SnapjawMage bit to the front of the name of the creature? Why not just call it a Snapjaw Mage?
Mopango pilgrim.png < When writing a mod, it's common courtesy to prefix unique identifiers with your name, an underscore _, followed by the mod's name. This ensures that if somebody else writes their own Snapjaw Mage mod, your mod won't conflict with theirs.

So far we haven't actually added anything to our creature; for all intents and purposes it's a plain ol' snapjaw. Let's change that by adding some parts.

Sprouting orb.png < What's a part?
Mopango pilgrim.png < A part is a special piece of code that can be added to a creature to modify its appearance, behavior, stats, and any number of other things. There are a lot of parts -- over a thousand! You won't need to worry about most of them; many of them are highly-customized bits of code written for just one or two creatures. For instance, there's a CryptSitterBehavior part that is used exclusively by crypt sitters.

We're going to add two parts to our creature: Render and Description.

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <object Name="Pyovya_SnapjawMage_Snapjaw Mage" Inherits="Snapjaw">
    <part Name="Description" Short="A tattered robe and decaying hat are all that protect =pronouns.possessive= thin layer of grizzled fur from the forces of nature. But behind that furtive glance, =pronouns.subjective= =verb:prepare:afterpronoun= an elemental spell, deployed at a moment's notice against threats to =pronouns.objective= and =pronouns.possessive= kin. =pronouns.Subjective= =verb:understand:afterpronoun= not the power that =pronouns.subjective= =verb:wield:afterpronoun=; and that only makes =pronouns.objective= all the more dangerous." />
    <part Name="Render" DisplayName="snapjaw mage" Tile="Assets_Content_Textures_Creatures_sw_snapjaw_warrior.bmp" ColorString="&amp;O" DetailColor="Y" />
  </object>
</objects>

Before I dig into the XML we've just added, save Creatures.xml. Reload your mods, and then wish for a Pyovya_SnapjawMage_Snapjaw Mage.

Mopango pilgrim.png < To reload your mods, return to the start screen for Caves of Qud, click on "installed mod configuration", and then press r. Then restart the game, either by continuing an old save or starting a new save.

You should see a snapjaw like the one below show up:

Snapjaw Mages -- Initial mage.webp

Congrats! You've successfully added a snapjaw mage to your game.

Sprouting orb.png < I did?

Well, it's still a little bare-bones, but you have indeed created a new creature called a snapjaw mage and spawned it in your game! Give yourself a pat on the back, Rulimispruce.

Sprouting orb.png < *rustling noises*

Now, back to the parts that you just added:

  • The Description part changes (as you might guess) the description of the creature when you look at it.
    • You'll notice that the description includes a lot of things like =pronouns.subjective= and =verb:understand:afterpronoun=. This ensures that the game uses the correct pronouns while generating the creature's description and conjugates verbs correctly. See Modding:Grammar for more details.
  • The Render attribute changes the creature's appearance, as well as its name (as it appears in-game). This tag has the following attributes:
    • DisplayName: how the creature's name is displayed in-game.
    • Tile: a .png or .bmp ("bitmap") image that's used to display the creature. Check out Modding:Tiles for more information. In the snippet above, I just used the tile for snapjaw warriors to get us started.
    • ColorString and DetailColor: these are the primary and secondary colors used to color in the tile that you supply. You can look at Modding:Colors & Object Rendering for a full list of all of the available colors; in this case, we used O (orange) as the primary color and Y (white) as the detail color.

Using custom tiles

Instead of having our snapjaw mages look like off-color snapjaw warriors, let's add a snazzy new tile to the game to use for our mages! Just pop open a pixel editor, create a 16px x 24px black-and-white tile, save it as a --

Sprouting orb.png < I'm just a lil old sprouting orb, I don't know how to do any of those things.

Ah, alright, fair enough. Well in that case, feel free to download the tile that I've already made for you here:

https://github.com/TrashMonks/Snapjaw-Mages/blob/main/Snapjaw-Mages/Textures/Pyovya_SnapjawMage/snapjaw_mage.png

In your mod directory, create a folder called Textures/; in that folder, create a subfolder called Pyovya_SnapjawMage/. Save snapjaw_mage.png there, so that your mod folder now looks like this:

Snapjaw-Mage
├── manifest.json
├── ObjectBlueprints
│   └── Creatures.xml
├── preview.png
└── Textures
    └── Pyovya_SnapjawMage
        └── snapjaw_mage.png

Let's change the Render part for our mages to use this tile:

<part Name="Render" DisplayName="snapjaw mage" Tile="Pyovya_Snapjaw/snapjaw_mage.png" ColorString="&amp;O" DetailColor="Y" />

Now let's fill out some more details about our furry friend!

Adding inventory

First of all, like any good mage, they need a walking stick and some books. Let's also give them some clothes to equip while we're at it. Add the following XML to your creature:

<inventoryobject Blueprint="Walking Stick" Number="1" />
<inventoryobject Blueprint="Cloth Robe" Number="1" />
<inventoryobject Blueprint="Sandals" Number="1" />
<inventoryobject Blueprint="StandaloneMarkovBook" Number="1-2" Chance="10" />

By setting Number="1-2" and Chance="10" in that last line, we've given our mages a 10% chance of holding 1-2 books when they spawn.

Adding skills

Let's give our mage the Cudgel skill as well, so that they're a little bit better at fighting with that walking stick:

<skill Name="Cudgel" />

Changing stats

The base Snapjaw creature that we're inheriting from is pretty weak, so we're going to bump up our mage's stats a little.

<stat Name="Hitpoints" Value="15" />
<stat Name="DV" Value="4" />

<!-- Raise ego to make the mage's mental mutations a little more powerful -->
<stat Name="Ego" sValue="19" />

<!-- Raise willpower to reduce the cooldowns of abilities -->
<stat Name="Willpower" sValue="19" />

If you've followed along up to this point, you should be ending this section with a snapjaw mage that looks like this:

Snapjaw Mages -- Mage new tile.webp

Your Creatures.xml at this point should look something like the following. Make sure that everything checks out before proceeding to the next section!

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <object Name="Pyovya_SnapjawMage_Snapjaw Mage" Inherits="Snapjaw">
    <part Name="Description" Short="A tattered robe and decaying hat are all that protect =pronouns.possessive= thin layer of grizzled fur from the forces of nature. But behind that furtive glance, =pronouns.subjective= =verb:prepare:afterpronoun= an elemental spell, deployed at a moment's notice against threats to =pronouns.objective= and =pronouns.possessive= kin. =pronouns.Subjective= =verb:understand:afterpronoun= not the power that =pronouns.subjective= =verb:wield:afterpronoun=; and that only makes =pronouns.objective= all the more dangerous." />
    <part Name="Render" DisplayName="snapjaw mage" Tile="Pyovya_SnapjawMage/snapjaw_mage.png" ColorString="&amp;O" DetailColor="Y" />

    <inventoryobject Blueprint="Walking Stick" Number="1" />
    <inventoryobject Blueprint="Cloth Robe" Number="1" />
    <inventoryobject Blueprint="Sandals" Number="1" />
    <inventoryobject Blueprint="StandaloneMarkovBook" Number="1-2" Chance="10" />

    <skill Name="Cudgel" />

    <stat Name="Hitpoints" Value="15" />
    <stat Name="DV" Value="4" />
    <stat Name="Ego" sValue="19" />
    <stat Name="Willpower" sValue="19" />
  </object>
</objects>

Using your creature as a base: Elemental Mages

Sprouting orb.png < Hold up a second. How is this creature a mage? It doesn't have any kind of magic spells!

I was just getting to that!

So far, we've sketched out most of the properties, skills, and stats that a snapjaw mage should have. Now we're going to create two types of mages: fire mages and ice mages.

First, we need to mark the snapjaw mage we've created as a "base object", i.e. an object that doesn't appear in-game but which instead serves as the basis for other objects. Add the following tag to the Pyovya_SnapjawMage_Snapjaw Mage creature:

<tag Name="BaseObject" Value="*noinherit" />

Now, below your definition of the snapjaw mage in Creatures.xml, add two new creatures:

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <!--
  Skipped: the XML that defines the "Pyovya_SnapjawMage_Snapjaw Mage" creature
  -->

  <object Name="Pyovya_SnapjawMage_Fire Mage" Inherits="Pyovya_SnapjawMage_Snapjaw Mage">
  </object>

  <object Name="Pyovya_SnapjawMage_Ice Mage" Inherits="Pyovya_SnapjawMage_Snapjaw Mage">
  </object>
</objects>
Mopango pilgrim.png < Notice how we've used the Inherits attribute here. We're telling the game that our original snapjaw mage creature, Pyovya_SnapjawMage_Snapjaw Mage, should serve as a base for our fire and ice mages. As a result, our creatures will spawn with walking sticks, have the Cudgel skill, and so on.

Referencing the Modding:Colors & Object Rendering page, we're going to change our fire mages' detail color to red. We're also going to give them the Pyrokinesis mutation:

<object Name="Pyovya_SnapjawMage_Fire Mage" Inherits="Pyovya_SnapjawMage_Snapjaw Mage">
  <part Name="Render" DisplayName="snapjaw {{R|fire}} mage" Tile="Pyovya_SnapjawMage/snapjaw_mage.png" ColorString="&amp;O" DetailColor="R" />
  <mutation Name="Pyrokinesis" Level="1" />
</object>

The {{R|fire}} bit there says that the word "fire" should be colored red. Now we'll make some similar changes to ice mages (changing their color to cyan), and give them the Cryokinesis mutation.

<object Name="Pyovya_SnapjawMage_Ice Mage" Inherits="Pyovya_SnapjawMage_Snapjaw Mage">
  <part Name="Render" DisplayName="snapjaw {{C|ice}} mage" Tile="Pyovya_SnapjawMage/snapjaw_mage.png" ColorString="&amp;O" DetailColor="C" />
  <mutation Name="Cryokinesis" Level="1" />
</object>

Now try wishing for a Pyovya_SnapjawMage_Fire Mage and a Pyovya_SnapjawMage_Ice Mage. You should get two creatures that appear like the ones below (and they should immediately start attacking you with Pyrokinesis and Cryokinesis!):

Snapjaw Mages -- Fire and Ice.webp

Creating new items

We've now successfully added a snapjaw fire mage and a snapjaw ice mage. We're also going to give them two new missile weapons: a tome of fire and a tome of ice. These weapons will be magical spell books that function as less powerful versions of the flamethrower and freeze ray, respectively.

Start by creating a file Items.xml in the ObjectBlueprints/ folder:

Snapjaw-Mages
├── manifest.json
├── ObjectBlueprints
│   ├── Creatures.xml
│   └── Items.xml
├── preview.png
└── Textures
    └── Pyovya_SnapjawMage
        └── snapjaw_mage.png
Sprouting orb.png < Does it matter whether I name my file Items.xml? And do I have to place it in the ObjectBlueprints/ directory?
Mopango pilgrim.png < Nope and nope! In fact, you can name all of your XML files whatever you want (as long as they end in .xml) and place them wherever you feel like (as long as they're in the SnapjawMage/ mod directory). The scheme that we're using here to organize our XML files is the same scheme that the game uses for its own files (the ones that you can find in the "game data files" folder), just to make the relationship between what we're writing and Caves of Qud's code a little bit clearer.

We want to create two new items -- a fire tome and and ice tome -- and we can probably guess in advance that these two items are going to have a lot of the same properties. Therefore, we're going to start the same way that we did when we were creating our mages: we'll create a base "magic tome" object, which we will later inherit from.

Add the following to Items.xml:

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <object Name="Pyovya_SnapjawMage_Magic Tome" Inherits="BaseMissileWeapon">
    <tag Name="BaseObject" Value="*noinherit" />
  </object>
</objects>

Now we need to add some parts to our object:

<!-- We borrow the same description that StandaloneMarkovBook uses -->
<part Name="Description" Short="Crisp pages of goatskin vellum are bound into a codex." />
<part Name="Physics" Weight="1" UsesTwoSlots="true" />
<part Name="Commerce" Value="80" />

We've already seen the Description part before. The Physics part is a common part used by items to determine how much they weigh, whether they are equipped in one or two slots, and what category they appear under in the inventory screen. Finally, the Commerce part defines how valuable the item is.

Let's also add a tag to the item to indicate its tier, as well as what mods it can accept (in our case, we don't want this item to be moddable):

<tag Name="Tier" Value="2" />
<tag Name="Mods" Value="" />

That should be enough for our base object. Now, let's create a fire tome and an ice tome by inherting from Pvovya_SnapjawMage_Magic Tome. While we're at it, let's add some tiles to render each of them; we'll use the built-in Items/sw_book_1.bmp tile (which looks like a regular book).

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <!-- Skipped: definition of "Pyovya_SnapjawMage_Magic Tome" -->

  <object Name="Pyovya_SnapjawMage_Fire Tome" Inherits="Pyovya_SnapjawMage_Magic Tome">
    <part Name="Render" DisplayName="tome of {{R|fire}}" Tile="Items/sw_book_1.bmp" ColorString="&amp;R" DetailColor="Y" />
  </object>

  <object Name="Pyovya_SnapjawMage_Ice Tome" Inherits="Pyovya_SnapjawMage_Magic Tome">
    <part Name="Render" DisplayName="tome of {{C|ice}}" Tile="Items/sw_book_1.bmp" ColorString="&amp;C" DetailColor="Y" />
  </object>
</objects>

Using existing items as a template

Now, we know that we want these items to function like a flamethrower and freeze ray, but we probably need to iron out some more details first. Such as:

Sprouting orb.png < Do magic books use ammo?

That's a good question. If we wanted, we could set up our new missile weapons to use some kind of magical ammo, or pull from an energy cell. I'm actually going to go with a third option -- let's set up our tomes of fire and ice so that they don't use any kind of ammo or energy, but instead just have a plain 10-turn cooldown.

We must also figure out what kind of projectile each of these items shoots. Let's take a look at how the game defines the flamethrower and freeze ray objects. Looking at the file locations, navigate in your file browser to where the game's data files are stored. Once you're in that folder, go to ObjectBlueprints/ and open up the Items.xml file. Then search for Flamethrower and Freeze Ray; you should find the following XML for each of them.

<object Name="Freeze Ray" Inherits="BaseRifle">
  <part Name="Render" DisplayName="{{freezing|freeze}} ray" Tile="items/sw_raygun.bmp" ColorString="&amp;C" DetailColor="y" />
  <part Name="Physics" UsesTwoSlots="true" Weight="28" />
  <part Name="MissileWeapon" Skill="Rifle" AmmoChar="FR" ShotsPerAction="1" AmmoPerAction="1" ShotsPerAnimation="1" WeaponAccuracy="0" />
  <part Name="Commerce" Value="750" />
  <part Name="EnergyAmmoLoader" ChargeUse="500" ProjectileObject="ProjectileFreezeRay" />
  <part Name="EnergyCellSocket" SlotType="EnergyCell" />
  <part Name="Description" Short="Gaseous coolant billows through a chiffon tube, putting dew on the wide and planar barrel chrome. It spills out onto the air from a ice-chipped muzzle." />
  <part Name="Examiner" Complexity="5" />
  <part Name="TinkerItem" Bits="12345" CanDisassemble="true" CanBuild="true" />
  <part Name="Metal" />
  <part Name="ItemElements" Elements="ice:10" />
  <tag Name="MissileFireSound" Value="lazerMedium4" />
  <tag Name="Mods" Value="MissileWeaponMods,FirearmMods,CommonMods,RifleMods,ElectronicsMods,BeamWeaponMods" />
  <tag Name="Tier" Value="5" />
  <tag Name="DynamicObjectsTable:Guns" />
  <stag Name="Cold" />
</object>

<!-- some more item definitions later... -->

<object Name="Flamethrower" Inherits="BaseHeavyWeapon">
  <part Name="Render" Tile="items/sw_flamethrower.bmp" DisplayName="flamethrower" ColorString="&amp;R" TileColor="&amp;c" DetailColor="R"/>
  <part Name="Physics" UsesTwoSlots="true" />
  <part Name="MissileWeapon" Skill="HeavyWeapons" bShowShotsPerAction="false" NoWildfire="true" MaxRange="12" AnimationDelay="20" AmmoChar="f" ShotsPerAction="9" AmmoPerAction="1" ShotsPerAnimation="9" WeaponAccuracy="10" />
  <part Name="Commerce" Value="100" />
  <part Name="LiquidAmmoLoader" Liquid="oil" ProjectileObject="ProjectileFlamethrower" />
  <part Name="LiquidVolume" MaxVolume="32" Volume="32" StartVolume="1d32" InitialLiquid="oil-1000" />
  <part Name="LeakWhenBroken" />
  <part Name="Description" Short="A limpid fuel pump exposes ribbons of oil slick, and behind it a voltage gauge ticks. The slanted heat shield is scorched black." />
  <part Name="Examiner" Alternate="UnknownBackpack" Complexity="4" />
  <part Name="TinkerItem" Bits="0004" CanDisassemble="true" CanBuild="true" />
  <part Name="Metal" />
  <tag Name="TurretName" Value="flamethrower turret" />
  <tag Name="Tier" Value="4" />
  <tag Name="UsesSlots" Value="Back,Missile Weapon,Missile Weapon" />
  <tag Name="DynamicObjectsTable:Guns" />
  <tag Name="ReloadSound" Value="SplashStep1" />
  <tag Name="MissileFireSound" Value="flamethrower" />
  <stag Name="Heat" />
</object>

If you know a bit about each of these items, you can intuit what some of these parts do. For instance, EnergyCellSocket is clearly related to the ability to insert and use energy cells in freeze rays; LiquidAmmoLoader is somehow connected to the fact that flamethrowers run on oil.

In any case, the most important bits right now is this line from the definition of Freeze Ray:

<part Name="EnergyAmmoLoader" ChargeUse="500" ProjectileObject="ProjectileFreezeRay" />

... and this line from the definition of Flamethrower:

<part Name="LiquidAmmoLoader" Liquid="oil" ProjectileObject="ProjectileFlamethrower" />

From this, we can see that freeze rays shoot a ProjectileFreezeRay object, and flamethrowers shoot a ProjectileFlamethrower object.

To have our new items shoot these projectiles on a cooldown, we're going to add the CooldownAmmoLoader part. Here's how we'll add it to our tome of fire:

<object Name="Pyovya_SnapjawMage_Fire Tome" Inherits="Pyovya_SnapjawMage_Magic Tome">
  <part Name="Render" DisplayName="tome of {{R|fire}}" Tile="Items/sw_book_1.bmp" ColorString="&amp;R" DetailColor="Y" />
  <part Name="CooldownAmmoLoader" Cooldown="10" Readout="true" ProjectileObject="ProjectileFlamethrower" />
</object>

... and here's how we'll add it to our tome of ice:

<object Name="Pyovya_SnapjawMage_Ice Tome" Inherits="Pyovya_SnapjawMage_Magic Tome">
  <part Name="Render" DisplayName="tome of {{C|ice}}" Tile="Items/sw_book_1.bmp" ColorString="&amp;C" DetailColor="Y" />
  <part Name="CooldownAmmoLoader" Cooldown="10" Readout="true" ProjectileObject="ProjectileFreezeRay" />
</object>

We also need to add a MissileWeapon part to each of our items, and specify the sound that the item makes when it's fired. Go ahead and update Items.xml until you get the following:

<?xml version="1.0" encoding="utf-8" ?>
<objects>
  <!-- Skipped: definition of "Pyovya_SnapjawMage_Magic Tome" -->

  <object Name="Pyovya_SnapjawMage_Fire Tome" Inherits="Pyovya_SnapjawMage_Magic Tome">
    <part Name="Render" DisplayName="tome of {{R|fire}}" Tile="Items/sw_book_1.bmp" ColorString="&amp;R" DetailColor="Y" />
    <part Name="MissileWeapon" AmmoChar="f" ShotsPerAction="1" AmmoPerAction="1" ShotsPerAnimation="1" WeaponAccuracy="0" />
    <part Name="CooldownAmmoLoader" Cooldown="10" Readout="true" ProjectileObject="ProjectileFlamethrower" />

    <tag Name="MissileFireSound" Value="flamethrower" />
  </object>

  <object Name="Pyovya_SnapjawMage_Ice Tome" Inherits="Pyovya_SnapjawMage_Magic Tome">
    <part Name="Render" DisplayName="tome of {{C|ice}}" Tile="Items/sw_book_1.bmp" ColorString="&amp;C" DetailColor="Y" />
    <part Name="MissileWeapon" AmmoChar="FR" ShotsPerAction="1" AmmoPerAction="1" ShotsPerAnimation="1" WeaponAccuracy="0" />
    <part Name="CooldownAmmoLoader" Cooldown="10" Readout="true" ProjectileObject="ProjectileFreezeRay" />

    <tag Name="MissileFireSound" Value="hiss_low" />
  </object>
</objects>

You've successfully added both items to your game! You can now wish for a Pyovya_SnapjawMage_Fire Tome or a Pyovya_SnapjawMage_Ice Tome.

Snapjaw Mages -- fire ice tomes.webp

Try equipping the item and firing it as a missile weapon. You should see a streak of fire appear when you shoot the tome of fire, and a streak of ice when you shoot the tome of ice.

Snapjaw Mages -- fire tome activated.webp

Snapjaw Mages -- ice tome activated.webp

Adding custom projectiles

Sprouting orb.png < I burnt down all of Joppa with my tome of fire! Take that, Yrame.

Ah, hm, yeah. The projectile that a missile weapon shoots defines the damage and effects of that weapon. We just decided to use the same projectile that flamethrower and freeze ray use, which in retrospect wasn't the greatest idea. Those are both mid- to late-game items, and we want our new snapjaws to be early-game enemies.

Let's fix that by creating some new projectile types that operate in the same way as ProjectileFlamethrower and FreezeRayProjectile, but are a little bit weaker. First, let's look up how these projectiles are defined in the game's data files; you can find them in (the game's version of) ObjectBlueprints/Items.xml.

We'll copy those definitions over to our own Items.xml in the folder for our mod. We'll rename these projectiles Pyovya_SnapjawMage_FireTomeProjectile and Pyovya_SnapjawMage_IceTomeProjectile:

<!-- Copied from ProjectileFlamethrower -->
<object Name="Pyovya_SnapjawMage_FireTomeProjectile" Inherits="TemporaryProjectile">
  <part Name="Render" DisplayName="{{R|stream of flame}}" ColorString="&amp;R" />
  <part Name="Projectile" BasePenetration="15" BaseDamage="1d2" Attributes="Heat Fire" ColorString="&amp;R" PassByVerb="whoosh" />
  <part Name="TemperatureOnHit" Amount="4d20" Max="false" OnWielderHit="true" />
  <part Name="TemperatureOnEntering" Amount="8d20" Max="false" OnWielderHit="true" />
</object>

<!-- Copied from ProjectileFreezeRay -->
<object Name="Pyovya_SnapjawMage_IceTomeProjectile" Inherits="TemporaryProjectile">
  <part Name="Render" DisplayName="{{C|energy beam}}" ColorString="&amp;B" />
  <part Name="Projectile" BaseDamage="1d4" Attributes="Cold NonPenetrating" ColorString="&amp;B" PassByVerb="crackle" />
  <part Name="TemperatureOnHit" Amount="-190" Max="false" OnWielderHit="true" />
  <part Name="TemperatureOnEntering" Amount="-190" Max="false" OnWielderHit="true" />
</object>

Let's make some changes to the penetration, base damage, and temperature changes of each of these items. Here's what I set mine to, but go ahead and try tweaking these numbers to different values!

<object Name="Pyovya_SnapjawMage_ProjectileFireTome" Inherits="TemporaryProjectile">
  <part Name="Render" DisplayName="{{R|stream of flame}}" ColorString="&amp;R" />
  <part Name="Projectile" BasePenetration="4" BaseDamage="1d4" Attributes="Heat Fire" ColorString="&amp;R" PassByVerb="whoosh" />
  <part Name="TemperatureOnHit" Amount="4d20" Max="false" OnWielderHit="true" />
  <part Name="TemperatureOnEntering" Amount="8d20" Max="false" OnWielderHit="true" />
</object>

<object Name="Pyovya_SnapjawMage_ProjectileIceTome" Inherits="TemporaryProjectile">
  <part Name="Render" DisplayName="{{C|streak of ice}}" ColorString="&amp;B" />
  <part Name="Projectile" BasePenetration="4" BaseDamage="1d4" Attributes="Cold" ColorString="&amp;B" PassByVerb="crackle" />
  <part Name="TemperatureOnHit" Amount="-4d20" Max="false" OnWielderHit="true" />
  <part Name="TemperatureOnEntering" Amount="-8d20" Max="false" OnWielderHit="true" />
</object>

Now you should change the CooldownAmmoLoader part of your tome of fire and tome of ice objects to use this new projectile.

Wrapping up

We can't forget to give our mages their new weapons! Go back to Creatures.xml, and add the following XML tag to Pyovya_SnapjawMage_Fire Mage:

<inventoryobject Blueprint="Pyovya_SnapjawMage_Fire Tome" Number="1" />

You should also add the tome of ice to Pyovya_SnapjawMage_Fire Mage's inventory:

<inventoryobject Blueprint="Pyovya_SnapjawMage_Ice Tome" Number="1" />

Now when you spawn one of your mages, you should find that they already have their magical tomes equipped.

Snapjaw Mages -- mage tome equipped.webp

Controlling spawning: population tables

Sprouting orb.png < Well, it looks like I can wish for a Pyovya_SnapjawMage_Fire Tome or a Pyovya_SnapjawMage_Ice Mage. But I can't find them anywhere in the game!

That's because there's one thing we're still missing: we haven't told Caves of Qud when and where our new mages should spawn!

Mopango pilgrim.png < In fact, it is already possible for our snapjaw mages to appear in game; it's just that the probability of this happening is very low at the moment. We'll talk about this more when we discuss dynamic tables.

Caves of Qud uses population tables to decide when to spawn different items and creatures. Here's an example of one such population table (which you can find in the game data in PopulationTables.xml):

<population Name="SaltMarshZoneGlobals">
  <table Name="StandardSurfaceGlobals" />
  <object Chance="90" Number="160-200" Blueprint="Watervine" />
  <object Chance="90" Number="90-130" Blueprint="Brinestalk" />
  <object Chance="50" Number="6-16" Blueprint="Dogthorn Tree" />
  <object Chance="25" Number="1-4" Blueprint="Witchwood Tree" />
  <object Chance="5" Number="1-4" Blueprint="Starapple Tree" />

  <object Chance="50" Blueprint="Croc" />
  <object Chance="75" Number="2d6" Blueprint ="Glowpad" Hint="Aquatic" />
  <object Chance="75" Number="2d4" Blueprint ="Glowfish" Hint="Aquatic" />
  <object Chance="50" Number="2d6" Blueprint ="GiantDragonfly" />
  <group Chance="4" Name="ManySnapjaws" Style="pickeach">
    <table Name="SnapjawParty1" Number="4" />
  </group>
</population>

This table is used in the salt marsh to help decide what animals and plants should be spawned. Let's take a look at a few of these lines:

  • <table Name="StandardSurfaceGlobals" />: this says that the SaltMarshZoneGlobals table should include all of the contents of another table called StandardSurfaceGlobals. You can find the definition of that table in PopulationTables.xml; it does things like add a chance of a Mechanimist pilgrim spawning in each zone.
  • <object Chance="50" Blueprint="Croc" />: this adds a 50% chance of a croc spawning in a given zone.
  • <group Chance="4" Name="ManySnapjaws" Style="pickeach">...</group>: this defines a "group" in the table. The usage here says (roughly speaking) that there's a 4% chance that we sample some creatures from the SnapjawParty1 table.

Adding snapjaw mages to population tables

There are a few different snapjaw-related population tables in the data files -- SnapjawParty0, SnapjawParty1, SnapjawParty1-with-Warlord, and SnapjawParty2. Each of these tables is used for different encounters with snapjaws.

In your Snapjaw-Mages mod folder, create a new file called PopulationTables.xml:

Snapjaw-Mages
├── manifest.json
├── ObjectBlueprints
│   ├── Creatures.xml
│   └── Items.xml
├── PopulationTables.xml
├── preview.png
└── Textures
    └── Pyovya_SnapjawMage
        └── snapjaw_mage.png

Within this file, start by adding the following:

<?xml version="1.0" encoding="utf-8" ?>
<populations>
</populations>

Next, we need to modify the contents of the four tables that we listed previously:

<?xml version="1.0" encoding="utf-8" ?>
<populations>
  <population Name="SnapjawParty0" Load="Merge">
  </population>
  <population Name="SnapjawParty1" Load="Merge">
  </population>
  <population Name="SnapjawParty1-with-Warlord" Load="Merge">
  </population>
  <population Name="SnapjawParty0" Load="Merge">
  </population>
</populations>
Sprouting orb.png < What does Load="Merge" do?
Mopango pilgrim.png < We need to specify Load="Merge" to tell Caves of Qud that we want to merge new XML into an existing table. This prevents us from creating a new population table like (for example) SnapjawParty0 that overwrites the old one.

It's possible to use Load="Merge" to modify the contents of most of the game's XML files.

Each of these tables has a group named Creatures, for instance:

<population Name="SnapjawParty0">
  <group Name="Creatures" Style="pickeach">
    <object Chance="100" Number="1-3" Blueprint="Snapjaw Scavenger 0" />
    <object Chance="75" Number="1-3" Blueprint="Snapjaw Scavenger 0" />
    <object Chance="25" Number="1-3" Blueprint="Snapjaw Hunter 0" />
    <object Chance="2" Number="1" Blueprint="Snapjaw Shotgunner 0" />
    <object Chance="15" Number="1" Blueprint="Snapjaw Brute 0" />
    <object Chance="10,3" Number="1" Blueprint="Snapjaw Warrior 0" />
    <object Chance="10" Number="1" Blueprint="Snapjaw Warlord 0" />
    <object Chance="3" Number="1" Blueprint="Snapjaw Hero 0" />
    <table Name="HumanoidEnvironmentFeatures" Chance="25" />
    <table Name="SnapjawParty0" Chance="10" />
  </group>
</population>

Because the group has Style="pickeach", the population table will go over each item (each XML tag) in the group and sample creatures based on the rules defined by that item:

  • Based on the first line (<object Chance="100" Number="1-3" Blueprint="Snapjaw Scavenger 0" />), there's a 100% chance that 1-3 snapjaw scavengers spawn).
  • The second line (<object Chance="75" Number="1-3" Blueprint="Snapjaw Scavenger 0" />) adds a 75% chance for an additional 1-3 snapjaw scavengers to spawn.
  • The fifth line (<object Chance="15" Number="1" Blueprint="Snapjaw Brute 0" />) adds a 15% chance for a snapjaw brute to spawn.
  • The last line (<table Name="SnapjawParty0" Chance="10" />) adds a 10% chance for another snapjaw party (also sampled from the SnapjawParty0 table) to spawn alongside this party.

To guarantee that each snapjaw party spawns with a fire mage and an ice mage, we'll merge some new XML into each of these groups:

<?xml version="1.0" encoding="utf-8" ?>
<populations>
  <population Name="SnapjawParty0" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Ice Mage" />
    </group>
  </population>

  <population Name="SnapjawParty1" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Ice Mage" />
    </group>
  </population>

  <population Name="SnapjawParty1-with-Warlord" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Ice Mage" />
    </group>
  </population>

  <population Name="SnapjawParty0" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Ice Mage" />
    </group>
  </population>
</populations>

Now when you encounter a band of roving snapjaws, they should include one fire mage and one ice mage:

Snapjaw Mages -- snapjaw party.webp

You can use the population:findblueprint wish to check the likelihood that a snapjaw mage will spawn in each of the population tables. For instance, here's a snippet from the popup that appears when you wish for population:findblueprint:Pyovya_SnapjawMage_Fire Mage:

Snapjaw Mages -- poptable probs.webp

As a result of the XML we merged into PopulationTables.xml, our mage has a 100% chance of appearing in each of the SnapjawMage tables. But we can also see that it has a nonzero chance of appearing in many other population tables; for instance, it has a 97% chance generating inside the waterlogged tunnel. That's because the SnapjawParty tables are used in turn by many other population tables for areas where snapjaws may be generated.

Creating new tables

Having two mages appear alongside every party of snapjaws feels like a lot -- ideally, we'd have either one fire mage or one ice mage show up. We could try using Chance to reduce the probability of each type of mage showing up, for example:

<object Number="1" Chance="50" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
<object Number="1" Chance="50" Blueprint="Pyovya_SnapjawMage_Ice Mage" />

But all that does is give us a 50% chance of spawning a fire mage, and a 50% chance of spawning an ice mage. That means that there's a 25% chance that both mages show up, and a 25% chance that neither mage shows up.

Instead, we'll define a new population table. This table will be set up so that whenever we sample from it, we either pick a fire mage or an ice mage:

<population Name="Pyovya_SnapjawMage_Mages">
  <group Name="Mages" Style="pickone">
    <object Number="1" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
    <object Number="1" Blueprint="Pyovya_SnapjawMage_Ice Mage" />
  </group>
</population>

By giving the Mages group Style="pickone", we're telling the game "whenever we pick an item from the Pyovya_SnapjawMage_Mages table, we want to pick just one of the items listed in our Mages group". Now we can change the XML that we merged into the SnapjawParty tables to sample from our new table instead. Our final Populations.xml table will look like this:

<?xml version="1.0" encoding="utf-8" ?>
<populations>
  <population Name="Pyovya_SnapjawMage_Mages">
    <group Name="Mages" Style="pickone">
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Fire Mage" />
      <object Number="1" Blueprint="Pyovya_SnapjawMage_Ice Mage" />
    </group>
  </population>

  <population Name="SnapjawParty0" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <table Name="Pyovya_SnapjawMage_Mages" />
    </group>
  </population>

  <population Name="SnapjawParty1" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <table Name="Pyovya_SnapjawMage_Mages" />
    </group>
  </population>

  <population Name="SnapjawParty1-with-Warlord" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <table Name="Pyovya_SnapjawMage_Mages" />
    </group>
  </population>

  <population Name="SnapjawParty0" Load="Merge">
    <group Name="Creatures" Load="Merge">
      <table Name="Pyovya_SnapjawMage_Mages" />
    </group>
  </population>
</populations>

After this change, snapjaw parties that we run across will only have one mage, rather than two.

Dynamic population tables

So far, the tables we've been working with have been static -- they're pre-defined based on the contents of PopulationTables.xml. It's also possible to generate a dynamic population table, which is generated based on the tags that creatures have as well as their relationship to one another. For instance, let's take a look at the definition of shrewd baboon in the game's ObjectBlueprints/Creatures.xml:

<object Name="Shrewd Baboon" Inherits="Baboon">
  <part Name="Render" DisplayName="shrewd baboon" ColorString="&amp;B" />
  <stat Name="AV" Value="3" />
  <stat Name="Intelligence" Boost="1" />
  <stat Name="Hitpoints" Value="20" />
  <property Name="Role" Value="Leader" />
  <tag Name="DynamicObjectsTable:Baboons" />
</object>

The <tag Name="DynamicObjectsTable:Baboons" /> XML at the end adds the shrewd baboon to the "Baboons" dynamic table.

Caves of Qud primarily uses dynamic tables when it needs to be be able to get one of a particular type of item or creature -- for instance, a random type of energy cell or a random type of goatfolk. This is useful, for example, for identifying what creatures are able to own a lair.

There are several ways to create a dynamic table:

  • Through DynamicObjectsTable, such as the DynamicObjectsTable:Baboons table in the previous example. These tables contain all objects explicitly tagged as belonging to that table.
  • Through the use of DynamicInheritsTable. These tables include all objects that inherit from another object. This inheritance doesn't need to be direct -- for example, even though shrewd baboon doesn't inherit from Creature (it inherits from Baboon), it would still fall under the dynamic inheritance table for Creature since Baboon inherits from BaseApe, which in turn inherits from Humanoid, and which finally inherits from Creature.
  • Through a DynamicSemanticTable. A dynamic semantic table contains all objects that fall into several intersecting categories, such as "medical" and "furniture".

Finally, it's also possible to exclude an object from a dynamic table by using ExcludeFromDynamicEncounters. Check out the Modding:Encounters and Population page for more information on how dynamic tables work.

Mopango pilgrim.png < Because snapjaw mages are descended from the Snapjaw object, which in turn ultimately descends from the Creature object, it turns out that snapjaw mages were already included in some population tables before we started writing PopulationTables.xml! In particular, snapjaw mages could appear in the DynamicInheritsTable:Creatures table, although the probability of them being generated was pretty low.

Returning to our mod: there's a Snapjaws dynamic table that we can add our mages to. Doing so is fairly straightforward: open up Creatures.xml and add the following tag at the end of the definition of Pyovya_SnapjawMage_Fire Mage and Pyovya_SnapjawMage_Ice Mage:

<tag Name="DynamicObjectsTable:Snapjaws" />

One result of adding this tag is that legendary snapjaw mage lairs can now appear! Here's one that I found while traversing the overworld:

Snapjaw Mages -- travelnote fire mage.webp Snapjaw Mages -- legendary fire mage.webp

Conclusion

Congrats! You reached the end of the tutorial!

Mopango pilgrim.png < Woohoo!
Sprouting orb.png < I'm ready to go face these mages in magical combat.

Extra challenge

Before you go, here's a little challenge that you can use to test what you've learned so far. We now have mages of fire and ice; now, try creating a new snapjaw electric mage, along with a tome of electricity that shoots electric projectiles. You're free to go about this however you wish, but here are some general pointers:

  • You'll probably want to give your new mage the Electrical Generation mutation.
  • When you're creating your tome of electricity, start by having it shoot ProjectileElectroPistol; this is the projectile that is shot by an arc winder. Once you're ready, create your own version of this projectile by referring to how ProjectileElectroPistol is implemented in the game's version of ObjectBlueprints/Items.xml.

Next steps

Sprouting orb.png < What should I do if I want to start designing my own creatures?

First off, make sure you read the other Modding pages! They're a treasure trove of valuable information about the various systems that exist in Caves of Qud and how to work with them.

You should definitely spend some time looking at how the game implements different creatures and items. This is the easiest way to learn about existing parts. If you have a good understanding of how a creature behaves in-game, you can look at how they're defined in Creatures.xml and intuit how the parts that have been applied to that creature affect its behavior.

Personally, I also find it helpful to look at other people's mods. There's a section in the Modding:Overview page that includes links to the source code for mods that people have written; it's a valuable reference for figuring out how other people have written mods for Caves of Qud. If you enjoyed making the mod for this tutorial, I'd specifically suggest checking out illuminatiswag's Terrors of the Depths mod (Steam Workshop page), which adds five new monsters to Caves of Qud. In addition to covering the concepts from this tutorial, it also goes a little more in-depth on concepts like C# scripting for creatures.

Finally, feel free to ask for help in the Caves of Qud and Kitfox Discords! We have a steadily-growing community of modders who are all excited to help you out. :)

Appendix: Mod debugging

Sprouting orb.png < Ahh! My mod isn't working! What can I do?

Finding and fixing bugs is a fundamental part of writing any kind of code! Don't feel bad if something isn't working the way you want it to on the first try; debugging is a skill that takes a while to develop.

There are four major ways in which the game will tell you that something has gone awry with your mod. The first is that -- for certain kinds of errors -- the game will display that the mod failed to build in your mod listing, and provide you with an error message. Here's what that looks like (using the Choose Your Fighter mod as an example):

CYF example error 1.webp

CYF example error 2.webp

The second way in which the game may tell you about errors -- specifically, errors related to Harmony patches -- is to write errors to your harmony.log.txt file.

These two methods are only used for the game to report certain kinds of C# errors. For the purposes of this tutorial, there are only two relevant error reporting mechanisms that the game will use:

  • the game will print the error to your Player.log file; and
  • an error popup will appear when starting the game (only if you have "Debug > Show error popups" enabled in your options).

Errors are reported in the same way through both mechanisms, so it doesn't really matter which one you use. However, it's generally a little more practical to read the Player.log file (once you've familiarized yourself with it), so in the next section we'll explore how this file works.

Reading the Player.log file

Player.log is a text file that gets created every time you start up Caves of Qud. It contains many different kinds of information about the game's boot process and things that occur in-game, such as any hardware that the game finds, events that occur when starting a new save, zone freezing/unfreezing events, and more.

The location of your Player.log file depends on what operating system you're using. Here is the location of that file at the time of this writing[1] (check the file locations page for an up-to-date resource):

Platform File type Path
Windows Player log %USERPROFILE%\AppData\LocalLow\Freehold Games\CavesOfQud\Player.log
macOS Player log ~/Library/Logs/Freehold Games/CavesOfQud/Player.log
Linux Player log ~/.config/unity3d/Freehold Games/CavesOfQud/Player.log

To read this file, open it up in the text editing or text viewing program of your choice. This file contains a lot of information, but for modders, there are really two things you want to search for:

  • MODWARN: lines beginning with MODWARN indicate warnings. Modders usually want to fix warnings, but they may not necessarily cause immediate problems in a mod. Modders usually want to fix these warnings, but they may not necessarily cause immediate problems in a mod.
  • MODERROR: these lines indicate important problems that a modder needs to address.
Mopango pilgrim.png < You're not likely to encounter MODWARN issues in the Snapjaw Mages mod, so we won't talk about them much. Here are some common reasons why you might get a warning, though:
  • You're trying to use a class or method that is deprecated (i.e. will be removed in a future version of the game).
  • You are using a skill or mutation in an object blueprint that has been renamed to something else.
  • You're trying to Load="Merge" into a blueprint or population table that doesn't exist.

You should generally try to fix warnings, although there are some legitimate reasons why they exist. For example, the Better Pet Selector mod merges some data into the blueprint of every pet provided via game DLC. However, since many players will not have all of the DLC required for all of the pets that exist, the mod will occasionally merge into nonexistent blueprints, producing a warning.

There are two broad classes of error that you may encounter while writing the Snapjaw Mages mod. The first is syntactic errors, and the second is semantic errors.

Syntactic errors

A syntactic error occurs when your mod's XML is not well-formed. This means that you've written code that doesn't conform to the rules of XML.

From experience, there are a few common kinds of syntactic errors that can arise. For example, say that you have a file, ObjectBlueprints/Test.xml with the following code inside of it:

<objects>
  <object Name="MySnapjaw">
</objects>

This XML is illegal because your <object> tag is unclosed -- that is, it's missing a corresponding </object> tag to say when the definition of your MySnapjaw object is finished. If you were to write XML like this, an error like the following would appear in your Player.log:

MODERROR [Snapjaw Mages!] - System.Exception: File: file:///home/pyovya/.config/unity3d/Freehold Games/CavesOfQud/Mods/Snapjaw-Mages/ObjectBlueprints/Test.xml, Line:
 3:3 ---> System.Xml.XmlException: The 'object' start tag on line 2 position 4 does not match the end tag of 'objects'. Line 3, position 3.
  at System.Xml.XmlTextReaderImpl.Throw (System.Exception e) [0x00027] in <29fb893c67d2441395d017f683ddfdf9>:0 
  at System.Xml.XmlTextReaderImpl.Throw (System.String res, System.String[] args) [0x00029] in <29fb893c67d2441395d017f683ddfdf9>:0 
  at System.Xml.XmlTextReaderImpl.ThrowTagMismatch (System.Xml.XmlTextReaderImpl+NodeData startTag) [0x00086] in <29fb893c67d2441395d017f683ddfdf9
>:0 
  at System.Xml.XmlTextReaderImpl.ParseEndElement () [0x0014d] in <29fb893c67d2441395d017f683ddfdf9>:0 
  ...
  at XRL.World.Loaders.ObjectBlueprintLoader.LoadAllBlueprints () [0x00074] in <18932719ef9e4b369067ceff1101084f>:0

This is a lot of information, but for Snapjaw Mages we only care about the first line[2]. The first line starts with MODERROR [Snapjaw Mages!] -- this confirms that it is in fact an error (and not just a warning), and that the error is coming from the Snapjaw Mages mod. The line also tells us the file in which the error occurs -- helpfully, in the Test.xml file that we were just writing. We are then given specific information about the error:

The 'object' start tag on line 2 position 4 does not match the end tag of 'objects'. Line 3, position 3.

In plain English, this is telling us that we have an <object> tag on line 2 that is closed by a </objects> tag on line 3, which is illegal because they're not the same kind of tag.

Sprouting orb.png < Hey! Didn't you just tell me that the issue was with the <object> tag? This error is telling us that the problem is with the closing </objects> tag!

Unfortunately the game's XML parser (the thing that reads XML) isn't terribly smart, so we have to decipher the real bug that's going on under the hood. The closing </objects> tag on line 3 is actually perfectly fine -- it's closing the opening <objects> tag we have on line 1. What we actually need to do to fix this problem is to add a closing </object> tag before </objects>, so that our XML looks like this:

<objects>
  <object Name="MySnapjaw">
  </object>
</objects>

Now the game should be able to read this XML file without issue.

Here's another example of a syntax error:

<objects>
  <object Name="MySnapjaw"
  </object>
</objects>

In this case, the problem is that the XML tag on line 2 is not well-formed -- it's missing an angle bracket > at the end.

Mopango pilgrim.png < Did you notice how the colorization of the code above looks wrong? That's because our code is being syntax-highlighted in this tutorial. The colorization makes it a lot easier to tell that there's a bug here. That's why it's really important to use a text editor with proper syntax higlighting, such as VS Code -- it makes it a lot easier to catch bugs in your code!

Here's how the error appears in the Player.log:

MODERROR [Snapjaw Mages!] - System.Exception: File: file:///home/pyovya/.config/unity3d/Freehold Games/CavesOfQud/Mods/Snapjaw-Mages/Test.xml, Line:
 3:3 ---> System.Xml.XmlException: Name cannot begin with the '<' character, hexadecimal value 0x3C. Line 3, position 3.
  at System.Xml.XmlTextReaderImpl.Throw (System.Exception e) [0x00027] in <29fb893c67d2441395d017f683ddfdf9>:0 
  at System.Xml.XmlTextReaderImpl.Throw (System.String res, System.String[] args) [0x00029] in <29fb893c67d2441395d017f683ddfdf9>:0 
  at System.Xml.XmlTextReaderImpl.Throw (System.Int32 pos, System.String res, System.String[] args) [0x0000c] in <29fb893c67d2441395d017f683ddfdf9
>:0 
  at System.Xml.XmlTextReaderImpl.ParseAttributes () [0x00181] in <29fb893c67d2441395d017f683ddfdf9>:0 
  ...
  at XRL.World.Loaders.ObjectBlueprintLoader.LoadAllBlueprints () [0x00074] in <18932719ef9e4b369067ceff1101084f>:0

Once again, the error doesn't tell us precisely what we want to know, so we have to decipher it a little bit. The error message says

Name cannot begin with the '<' character, hexadecimal value 0x3C. Line 3, position 3.

If we interpret this error as literally as possible, the game is complaining because we are trying to start the name of an XML attribute with the character <. Recall from #XML that an attribute is data that appears inside of an XML tag -- for example, Name="MySnapjaw" is an XML attribute.

So the game thinks that we're trying to add an attribute to our <object> tag that starts with the character <, which is not allowed in XML. But that's not what we wanted to do with line 3 -- we were trying to close our XML tag! The root of the problem here is that the game never found the end of our <object> tag, so when we started writing the </object> closing tag it assumed that that was still a part of the data from the initial tag.

Mopango pilgrim.png < Syntactic errors can be frustrating because they don't always tell you exactly what's wrong, so you're frequently searching the code around the error to find where the real issue is. When you're reading an XML error message, you need to think from the perspective of the game's XML parser -- "how might a program reading my XML come up with this error?" Once you've gained some experience debugging a few syntax errors you can identify common patterns that cause these bugs to crop up. You should always use an editor with XML syntax highlighting (and other related features, e.g. automatic indentation) to make it easier to find these errors.

Semantic errors

A semantic error occurs when your XML is syntactically correct, but the data that is stored in your XML cannot be understood (or is misunderstood) by the game for one reason or another. Here's an example:

<objects>
  <object Name="MySnapjaw" Inherits="Snapjaw Scvenger">
  </object>
</objects>

Did you see the problem? Our MySnapjaw object inherits from a misspelled blueprint -- Snapjaw Scvenger rather than the (correct) Snapjaw Scavenger. The game provides a very helpful explanation of this issue in our Player.log:

MODERROR [Snapjaw Mages!] - blueprint "Snapjaw Scvenger" inherited by MySnapjaw not found

The mod is generating an error because it doesn't know what a Snapjaw Scvenger is. If we fix the typo in the word "Scavenger" then this error will no longer exist. Easy peasy!

Well, this error was easy enough anyways. Sometimes semantic errors can be a lot trickier to debug. In the worst-case scenario, some errors might not even produce an error message at all:

<objects>
  <object Name="MySnapjaw" Inherits="Snapjaw Scavenger">
    <!--
      Give our creature the name "snapjaw mage", use dark orange for its primary color and white
      for its detail color. The creature will inherit its tile from Snapjaw Scavenger.
    -->
    <part Name="Render" DisplayName="snapjaw mage" ColorString="O" DetailColor="Y" />
  </object>
</objects>

The error here can be difficult to identify, and it produces no error message whatsoever. If you wrote this XML, you would only notice that there's a bug when you wished for a MySnapjaw to appear:

Snapjaw mage color error.webp

The problem here is that our creature's colors are incorrect. In line 7 of our XML, we wanted to make dark orange the primary color of our new creature, but instead our creature was colored a light grey. The source of the issue is that we specified our primary color as ColorString="O" rather than ColorString="&amp;O", which mean two different things. Even experienced modders can get tricked by bugs like these.

There isn't a tried-and-true method for debugging issues like these. Some techniques that I recommend include:

  • Use a version control system, like Git. A full version control guide is outside the scope of this tutorial, but many can be found online. Commands like git diff can tell you what changes you've made to your code and help you figure out where you may have introduced a bug.
  • Identify what parts of your object blueprint might relate to the bug that you are witnessing. In the example above, the colorization of our tile is incorrect. Since tiles and their colors are controlled by the Render part, we can infer that the issue has something to do with our snapjaw's Render part.
  • Think about what blueprints your object is inheriting from. Sometimes parent blueprints can come with parts that override attributes on child blueprints. For example, if object Foo has a RandomTile part and object Bar inherits from Foo, then Bar will also inherit the RandomTile part. This can cause a custom tile set for Bar to be silently overridden.
  • Enable the "Debug > Show debug info on objects" option and use it in-game to look at object internals.

You can find a more extensive discussion of debugging techniques in Modding:Debugging.

Footnotes

  1. Game version 2.0.209.39.
  2. The remaining lines give us a stack trace that would be very helpful if we were writing a mod with C# code.