Today we are going to talk about Classes
The Class System allows for a user defined upgrade structure for PC characters in Fortress mode. The key features are
- Working experience system - Gain experience through killing, using interactions, and reactions
- Class requirements - Restrict classes based on experience, attributes, traits, skills
- Class bonuses - Gain attributes and skills based on class level
- Class trees - Create complicated class trees by requiring other classes
As you can see it allows for lots of different customization! So let's get started. First off, let's go over what you will need.
- Dwarf Fortress
- DFHack
- My Script Collection
- Python v3+ (might work with 2.7, have not tried)
The files in my script collection that are related to the Class System
- hack/lua/classes/establish-class.lua
- hack/lua/classes/read-file.lua
- hack/lua/classes/requirements-class.lua
- hack/lua/classes/requirements-spell.lua
- hack/scripts/classes/add-experience.lua
- hack/scripts/classes/change-class.lua
- hack/scripts/classes/learn-skill.lua
- hack/scripts/base/classes.lua
- hack/scripts/unit/attribute-change.lua
- hack/scripts/unit/skill-change.lua
- hack/scripts/unit/trait-change.lua
- raw/objects/classes.txt
- raw/objects/spells.txt
- raw/classes_setup.py
So, where to start? For virtually everything you want to do, the only two files you will need to work with are the two .txt files, classes.txt and spells.txt. classes is where you will specify everything related to the classes, and spells is used to coordinate reactions/inorganics/syndromes and everything else needed for ease of use.
Let's look at classes.txt:
This text file will contain all of your defined classes, each following a specific format. The structure of the classes can be broken down into four separate parts, the base, bonuses, requirements, and spells
[CLASS:SQUIRE]
# Base tokens
[NAME:squire]
[EXP:10:20]
[LEVELS:2]
# Bonus tokens
[BONUS_PHYS:STRENGTH:50:75:100]
[BONUS_MENT:WILLPOWER:10:20:30]
[BONUS_SKILL:AXE:1:2:2]
[BONUS_TRAIT:ANGER:-5:-5:-5]
# Requirement tokens
[REQUIREMENT_PHYS:STRENGTH:1500]
[REQUIREMENT_MENT:WILLPOWER:1000]
[REQUIREMENT_SKILL:AXE:4]
[REQUIREMENT_TRAIT:ANGER:45]
[REQUIREMENT_CLASS:PEASANT:1]
[REQUIREMENT_COUNTER:TRAIN:5]
[FORBIDDEN_CLASS:ACOLYTE:1]
# Spell tokens
[SPELL:SPELL_TEST_1:0]
[SPELL_REQUIRE_PHYS:AGILITY:1500]
[SPELL_REQUIRE_MENT:FOCUS:1500]
[SPELL_FORBIDDEN_CLASS:ACOLYTE:0]
[SPELL_FORBIDDEN_SPELL:SOME_OTHER_SPELL]
[SPELL_COST:100]
[SPELL_UPGRADE:SOME_OTHER_SPELL]
Those are all of the currently supported tokens for each class. You can have as many or as few of each that you want (e.g. you can require multiple physical attributes or none)
Now to looks at the tokens individually and see what each one does.
Base tokensThese tokens are the only mandatory tokens for a class
- [NAME] specifies what the class is called in-game, and what name appears next to your dwarf (e.g. Squire Urist McDwarf)
- [LEVELS] specifies how many different levels a class has
- [EXP] specifies the required experience amount for each level, note that you need as many numbers here as you have levels
Bonus tokensThese tokens give your dwarf extra bonuses for being the class, and for each level, note that, unlike experience, you need to have 1 + the number of levels, where the first number signifies the bonus for level 0. You can have any number of these bonuses.
- [BONUS_PHYS] - adds (or subtracts) a set amount from the units specified physical attribute, the amount is total, not cumulative, so a level 2 Squire has a total of +100 strength, not +225
- [BONUS_MENT] - same as [BONUS_PHYS] except for the mental attributes
- [BONUS_SKILL] - same as [BONUS_PHYS] except for the units skills
- [BONUS_TRAIT] - same as [BONUS_PHYS] except for the units traits
Requirement tokensThese tokens place restrictions on the class and which Dwarfs can be the class. Unlike bonuses there is only one number needed, as bonuses are checked for becoming the class, not for each level.
- [REQUIREMENT_PHYS] - this states that the unit must have a minimum amount of the specified physical attribute in order to become the class
- [REQUIREMENT_MENT] - same as [REQUIREMENT_PHYS] except for mental attributes
- [REQUIREMENT_SKILL] - same as [REQUIREMENT_PHYS] except for skills
- [REQUIREMENT_TRAIT] - same as [REQUIREMENT_PHYS] except for traits
- [REQUIREMENT_CLASS] - this states that the unit must have reached the specified level in the specified class
- [REQUIREMENT_COUNTER] - this is to be used with my counters script, and so is outside of the scope of this tutorial
- [FORBIDDEN_CLASS] - this works in conjunction with [REQUIREMENT_CLASS] except instead of needing the specified class at the specified level, it forbids a unit of class/level from being this class
Spell tokensHere is where the classes get interesting. You can only learn specific spells (i.e. interactions) if you are a specific class. Each spell is defined in the same way, and comes with it's own set of special tokens
- [SPELL] - this always starts off the defining of a spell and is the only mandatory token, the name is arbitrary, but must be unique, the number is the level at which the spell can be learned by the class. Instead of a number, 'AUTO' can be placed instead, this will mean that, as soon as the Dwarf becomes the class, it will learn those spells (as opposed to being taught through reactions)
- [SPELL_REQUIRED_PHYS], [SPELL_REQUIRED_MENT], and [SPELL_FORBIDDEN_CLASS] - these work the same as the class versions, except dictate whether the unit can learn the spell
- [SPELL_FORBIDDEN_SPELL] - this only lets a unit learn this spell if it hasn't learned the specified forbidden spell
- [SPELL_COST] - this is an advanced tag that I will touch on later, by default the cost of learning all spells is set to 0
- [SPELL_UPGRADE] - instead of learning a completly new spell, you will instead forget an old spell and learn this one in it's place (in game terms, you will lose the syndrome that gave you the previous spell, and gain the syndrome that gives you this spell, instead of keeping both)
Misc tokensThere is currently only one other token available besides the above mentioned, and that is the [AUTO_UPGRADE] token. Formatted like
[AUTO_UPGRADE:WARRIOR]
this token tells the game that as soon as the max level of the class is reached, to change the units class to the WARRIOR class (e.g. when you reach SQUIRE level 2, change to WARRIOR level 0). This simplifies some of the micro-management of certain class trees.
So now you know how to set up your classes.txt file, note that there is no limit to the number of classes you can have, but each one must have a unique identifier (e.g. SQUIRE)
Now we will take a look at the spells.txt file, this file will help you set up everything you need in game, and, along with the python routine, automate several steps. This file is very basic
[SPELL:SPELL_TEST_1] <- simply label each [SPELL] as they are labeled in the classes.txt file
[CDI:INTERACTION:SPELL_FIRE_FIREBALL] <- and place any interaction information you would normally have here
[CDI:ADV_NAME:Fire Ball]
[CDI:TARGET:C:LINE_OF_SIGHT]
[CDI:TARGET_RANGE:C:15]
[CDI:USAGE_HINT:ATTACK]
[CDI:VERB:cast Fire Ball:casts Fire Ball:NA]
[CDI:TARGET_VERB:is caught in a ball of fire:is caught in a ball of fire]
[CDI:MAX_TARGET_NUMBER:C:1]
[CDI:WAIT_PERIOD:2000]
That's it!
Now we move to the python script. With classes.txt and spells.txt placed in your raw/objects/ folder and the python placed in the raw/ folder. Run the python script. If all goes well it will generate four text files;
- dfhack_input.txt
- Simply copy and paste the information from dfhack_input.txt into onLoad.init in your raws/objects folder
- inorganic_dfhack_class.txt
- Double check to make sure it looks correct, then simply move the file into your raws/objects folder
- permitted_reactions.txt
- Copy and paste this text into your desired entity
- reaction_classes.txt
- Here is where you have to do some extra work
- If you have a CDI:ADV_NAME in spells.txt you will see it appear in the NAME of the reaction, otherwise you will see #YOUR_SPELL_NAME_HERE#, replace this with your desired spell name
- In the BUILDING of the reaction, you will see #YOUR_BUILDING_HERE#, replace this with your desired building name
- You will notice there are no skills, reagents, or products associated with these reactions. While none are necessary, you may wish to add material costs to changing classes or learning spells
- Once you are happy with your changes, simply move the file into the raws/objects/ folder
And now you are all set to start using classes!
Addendum 1: Experience systemBy default the game awards 1 experience point for each kill, whether it be a turtle or a dragon, to address this issue there are several avenues a modder can take.
- Adding [CREATURE_CLASS:EXPERIENCE_X], where X is some positive integer, will instead mean that killing that creature rewards X amount of experience
- In hack/scripts/base/classes.lua, at the top of the file, you will see radius = -1, this is the default behavior, and means that only the unit that struck the killing blow (in truth, only the unit listed as LAST_ATTACKER in DFHack when the target dies) will gain the experience. Increasing the number to above 0 means that any friendly unit within the radius of the unit who struck the killing blow will receive the experience.
- Experience can be gained through reactions by placing
modtools/reaction-trigger -reaction 'YOUR_REACTION_HERE' -command [ classes/add-experience -unit \\WORKER_ID -amount X ]
into your onLoad.init, and every time you run the given reaction, you will gain X experience for your current class - Modders can also add experience gains to interaction usage (this allows for classes like healers, who will rarely kill anything, to still gain experience). This experience is not shared over nearby units if the radius is increased, but instead is just for the user of the interaction. To do this simply place;
modtools/interaction-trigger -onAttackStr 'YOUR_CDI:VERB_HERE' -command [ classes/add-experience -unit \\ATTACKER_ID -amount X ]
into your onLoad.init, and every usage of the interaction will award you with X experience for your current class
These options allow for earning experience to be smoother and more reliable.
Addendum 2: Spell costsIn addition to class and global experience, the system also tracks, what I call, skill experience. You can think of this as the "skill points". By default all spells cost 0 skill points to learn. Increasing this number means that a unit will spend these skill points to learn the spell. An example;
Unit becomes class Squire
Unit kills 20 experience worth of creatures
Unit now has 20 class experience, 20 global experience and 20 skill experience
Unit learns a spell that costs 10 skill points
Unit now has 20 class experience, 20 global experience, and 10 skill experience
Unit then changes to class Warrior
Unit kills 10 experience worth of creatures
Unit now has 10 class experience, 20 global experience, and 20 skill experience
In the future it may be possible to relate skill experience to levels gained, instead of experience gains, but for now, the system is set with experience.