Building a Class Mod - BG3-Community-Library-Team/BG3-Community-Library GitHub Wiki

Class Mods

Class Mods are an exciting way to add more playstyles to the game. Rather than being limited to the existing classes, you can completely reinvent gameplay.

Table of Contents

  1. Getting Started
  2. Class Descriptions
  3. Ability Distribution Presets
  4. Progressions
  5. Localization
  6. Abilities, Passives, and Spells
  7. Passive and Spell Lists
  8. Custom Equipment
  9. Custom Icons
  10. Skill Icons
  11. Handling Subclasses

Getting Started

There's a lot that goes into creating a custom class, but overall it's one of the easier things to do. This guide will go with the assumption that you're building this with the Community Library in mind. If you want to create a class, but don't want to include any parts of it in the community library, this guide will still be applicable, but you may need to make some modifications. You'll also want to skip the rest of this step and head straight to File Layout.

Building a mod for Community Library involves adding the base components into the Community Library, and building a separate Implementation Mod. To get started, make sure you've followed the steps outlined in our How to Contribute and Setting up a Development Environment pages, and have pulled down your own copy of the Community Library.

What you need

  1. VSCode, or other preferred text editor/IDE
  2. Git
  3. A local fork of Community Library
  4. LSLib
  5. Modder's Multitool

Now, before we start, let's just make sure we have the latest code.

  1. Open Gitbash in your Community Library folder.
  2. Input get checkout main to navigate to the main branch.
  3. Input git fetch && git pull to make sure you have the latest version of the project
  4. Finally, Input git checkout -b BRANCHNAME, substituting BRANCHNAME with a descriptive name of your branch.

File Layout

For our Implementation Mod, we'll need to set up a file structure, outside of our Community Library folder. Let's assume something like this, and add and remove as we go:

ModName/ (Folder which will contain your mod's folder, zip files, and any other files that won't be converted into a .pak file) 
    ModName/
        /Localization/English/ModName.xml
        /Mod/ModName/meta.lsx
        /Public/ModName/...

Of course, sub English with whichever language you want to release your mod in. For the sake of this tutorial, we'll assume English.

Class Descriptions

We're going to start with the most unintuitively named part of the whole Class workflow. Class Descriptions have nothing to do with Localization - these nodes are, in fact, where we define our Class. This is done in Community Library. Class Descriptions are stored in Public/ModName/ClassDescriptions/ClassDescriptions.lsx. The nodes aren't too lengthy, so let's take a quick look at an example provided by MailMe's Death Knight, graciously donated to the Community Library:

                <node id="ClassDescription"> 
                    <attribute id="BaseHp" type="int32" value="12"/>
                    <attribute id="CanLearnSpells" type="bool" value="false"/>
                    <attribute id="CharacterCreationPose" type="guid" value="0f07ec6e-4ef0-434e-9a51-1353260ccff8"/> 
                    <attribute id="ClassEquipment" type="FixedString" value="EQP_CC_DeathKnight"/> 
                    <attribute id="Description" type="TranslatedString" handle="h8f688ae8g8104g4542g94cfge4d0b1d5318c" version="1"/> 
                    <attribute id="DisplayName" type="TranslatedString" handle="h0ac042b8g9c9eg40f3g88dag83496ad4b8a8" version="1"/> 
                    <attribute id="HpPerLevel" type="int32" value="7"/> 
                    <attribute id="LearningStrategy" type="uint8" value="1"/>
                    <attribute id="MustPrepareSpells" type="bool" value="false"/> 
                    <attribute id="Name" type="FixedString" value="DeathKnight"/> 
                    <attribute id="PrimaryAbility" type="uint8" value="3"/> 
                    <attribute id="ProgressionTableUUID" type="guid" value="29b323b1-d1fa-4882-a8f3-d4cb0ad6c158"/> 
                    <attribute id="SoundClassType" type="FixedString" value="Fighter"/> 
                    <attribute id="SpellCastingAbility" type="uint8" value="5"/>
                    <attribute id="UUID" type="guid" value="52296c38-fbb5-4e3a-a728-0420884ed152"/> 
                    <children>
                        <node id="Tags">
                            <attribute id="Object" type="guid" value="c7bdcdf2-15e7-456e-adf1-21dda3172e18"/> 
                        </node>
                        <node id="Tags">
                            <attribute id="Object" type="guid" value="1ae7017c-4884-4a43-bc4a-742fa0d201c0"/> 
                        </node>
                    </children>
                </node>

A Closer Look

Looking at the above example, we can see there are a good number of attributes. Let's go over what we're seeing:

  • BaseHp - This is the Base HP value of the class(what you'd expect the class to have at level 1).
  • CanLearnSpells - This refers to the Wizard's ability to learn spells from scrolls.
  • CharacterCreationPose - This references the pose that the class should have during Character Creation. This is typically the same value for all playable classes.
  • ClassEquipment - This matches the ID(Name) of an equipment entry, something we'll go over later on in the guide.
  • Description - This is the localization handle for our Class' description.
  • DisplayName - This is the localization handle for our Class' displayed name.
  • HpPerLevel - The base amount of HP gained per level.
  • LearningStrategy - Going to level with you, I'm not totally sure what this actually translates to, but the value for most classes is 1. For Archfey Warlock, it's 2.
  • MustPrepareSpells - A boolean value on whether or not the class must prepare their spells, if they have any.
  • Name - The internal reference to this Class.
  • PrimaryAbility - Primary attribute for the class. Maps to the attributes, 1 = STR, 2 = Dexterity, 3 = Constitution, 4 = Intelligence, 5 = Wisdom, 6 = Charisma
  • ProgressionTableUUID - Maps to the TableUUID value of the Class' Progressions .
  • SoundClassType - Refers to the Sounds this class makes.
  • SpellCastingAbility - Similar to PrimaryAbility, but identifies the Class' Spellcasting Modifier.

We can also add tags to the class, as you can see.

Making a New Node

So what do we do with this? Let's start by copy/pasting it as a new Race node, and making our changes.

  1. Using Modder's Multitool, tick "Handle", and generate a new UUID.
  2. Copy the UUID(click on it's display), and paste it into Description's handle value.
  3. Do the same for DisplayName.
  4. Write an internal name, prefixed with CL_ to help ensure there aren't any collisions with other mods
  5. Set the ClassSoundSwitch based on another similar class.
  6. Untick "Handle" in Modder's Multitool, and generate a new UUID, pasting it into the UUID attribute's handle field.
  7. Set your Class' base HP value.
  8. Set your Class' Primary Attribute and Spellcasting Modifier Attribute.
  9. Decide if you want your class to learn spells from scrolls, or if you class must prepare spells to use them.

Leave the Progression UUID field blank for now. We'll come back to that in the Progressions section. Same goes for Class Equipment. Once you've finished making your class' ClassDescription node, we'll move on to Ability Distribution.

Ability Distribution Presets

The Ability Distribution Presets define the presets for a class' Attributes in Character Creation. You'll want to set it up in your implementation mod, at this location:Public/ModName/AbilityDistributionPresets/AbilityDistributionPresets.lsx. Overall a fairly simple piece, an AbilityDistributionPreset node looks something like this:

                <node id="AbilityDistributionPreset">
                    <attribute id="ClassUUID" type="guid" value="52296c38-fbb5-4e3a-a728-0420884ed152"/>
                    <attribute id="Charisma" type="int32" value="8"/>
                    <attribute id="Constitution" type="int32" value="15"/>
                    <attribute id="Dexterity" type="int32" value="8"/>
                    <attribute id="Intelligence" type="int32" value="8"/>
                    <attribute id="Strength" type="int32" value="15"/>
                    <attribute id="Wisdom" type="int32" value="15"/>
                    <attribute id="UUID" type="guid" value="5cd2d546-d19c-4271-9f11-8577b25d51f2"/>
                </node>

It's fairly self-descriptive, so I won't break down what each piece means. They key point here is that you need to reference your class, generate an ID for the preset, and assign base stat values for each of the listed attributes. Note that while this file looks general enough to be placed in Community Library, because it's in the CharacterCreationPresets folder, we recommend keeping it in your Implementation mod for now.

Progressions

One issue you'll run into if you've attempted to test the mod already: The class, though it's defined, doesn't show up in Character Creation. This is because it lacks a Level 1 Progression. As Progressions are what define our class as implemented, this is something we'll do in our Implementation Mod. Make a Progressions folder in Public/ModName/, and create a file called Progressions.lsx, with the following boilerplate:

Boilerplate

<?xml version="1.0" encoding="UTF-8"?>
<save>
  <version major="4" minor="0" revision="9" build="330"/>
    <region id="Progressions">
        <node id="root">
          <children>

          </children>
        </node>
    </region>
</save>

Everything we add to this file will be between the two children tags under the root node.

Example Progression Node

Now that we have our basis for a Progressions file, let's take a look at what a Progression Node looks like:

            <node id="Progression"> 
              <attribute id="AllowImprovement" type="bool" value="false"/> 
              <attribute id="Boosts" type="LSString" value="ProficiencyBonus(SavingThrow,Wisdom);ProficiencyBonus(SavingThrow,Constitution);Proficiency(Greatswords);Proficiency(HeavyArmor);Proficiency(MediumArmor)"/> 
              <attribute id="Level" type="uint8" value="1"/> 
              <attribute id="Name" type="LSString" value="DeathKnight"/>
              <attribute id="ProgressionType" type="uint8" value="0"/> 
              <attribute id="Selectors" type="LSString" value="SelectSkills(6a4f8598-3a17-42d1-beac-d7ced582bafa,2);AddSpells(f8799582-ef4c-49b7-88eb-2120aecab569);SelectPassives(da3203d8-750a-4de1-b8eb-1eccfccddf46,1,FightingStyle);SelectAbilityBonus(b9149c8e-52c8-46e5-9cb6-fc39301c05fe,AbilityBonus,2,1)"/>
              <attribute id="TableUUID" type="guid" value="29b323b1-d1fa-4882-a8f3-d4cb0ad6c158"/> 
              <attribute id="UUID" type="guid" value="9406edff-5e57-4eb6-ab0d-a2cc7d5d8b8a"/> 
            </node>

Pretty straightforward, right? Still, let's break it down:

  • AllowImprovement
  • Boosts - Any benefits your Class receives. Typically Proficiency() and Resistance() function calls, but pretty much anything that can work in a Status Boost field should work here.
  • Level - The level at which this progression is granted.
  • Name - The internal identifier, typically the same as your Class' Name value.
  • ProgressionType - This is typically 0, 1, or 2. For the purposes class progressions, the value will usually be 0. 0 = Class, 1 = Subclass, and 2 = Race.
  • Selectors - A semi-colon separated list of functions that allow you to have the user receive or select passives, spells, skills, ability bonuses, and more. We'll touch on this more when we get to the Passive & Spell Lists section.
  • TableUUID -The Identifier for tis series of progressions - more in a bit.
  • UUID - The Identifier for this specific Progression.

Multiclass

Having the above is great, but say you want to enable multiclassing for this class. It's fairly simple, but here's an example:

            <node id="Progression"> 
              <attribute id="AllowImprovement" type="bool" value="false"/>
              <attribute id="Boosts" type="LSString" value="Proficiency(Greatswords);Proficiency(MediumArmor)"/>
              <attribute id="IsMulticlass" type="bool" value="true"/>
              <attribute id="Level" type="uint8" value="1"/>
              <attribute id="Name" type="LSString" value="DeathKnight"/>
              <attribute id="ProgressionType" type="uint8" value="0"/>
              <attribute id="Selectors" type="LSString" value="AddSpells(f8799582-ef4c-49b7-88eb-2120aecab569);SelectPassives(da3203d8-750a-4de1-b8eb-1eccfccddf46,1,FightingStyle)"/>
              <attribute id="TableUUID" type="guid" value="29b323b1-d1fa-4882-a8f3-d4cb0ad6c158"/>
              <attribute id="UUID" type="guid" value="ae89fb12-155e-4ad9-bbf7-bfe67774d8e3"/>
            </node>

We can see that this Progression node looks very similar to the above. We have a few less values in the Selectors attribute, but otherwise, we just have a new attribute, IsMulticlass. This being set to true flags to the game that "Hey, this class needs to be available as a selection when opting to multiclass."

Higher Levels

Of course, any class is going to need more than just their level 1 progression. You'll want to have progression nodes at any level where the player should be getting a special bonus or mechanical change, like a new Feat, Action, or Spell.

TableUUID

So, the difference between TableUUID and UUID is important. Generally, when you're working with Classes, Races, and anything else with a progression, you want to reference the TableUUID rather than the regular UUID. This is because the game reads each Node with an identical TableUUID as part of a series, and as you can guess, a single Class or Race can have a lot of Progression Nodes related to them.

Testing

Once you've set up your Progressions the way you like, copy your TableUUID field, and paste it into your ClassDescription node's ProgressionTableUUID attribute. Now you're ready to test.

  1. Pack Community Library and your Implementation Mod, then load it into your game.
  2. Start a new game
  3. Go to the Class Selection UI

You should now see your class at the bottom of the list of Classes. You may notice an issue though - the text says "Not Found." Let's fix that in the next section.

Localization

If you've read the guide to setting up a custom race, you likely already know what to do, so feel free to skip this section if so. Otherwise, let's get into it:

An Overview

In your Implementation Mod, create a file, Localization/English/ModName.xml. Paste in this boilerplate:

<contentList date="8/21/2023 5:00">
  <content contentuid="" version="1"></content>
  <content contentuid="" version="1"></content>
</contentList>

contentui is the handle that the game reads to identify which line goes where. The text within each Content element is what will display on the screen. If nothing is defined for a given handle, you'll see "Not Found" in-game.

Writing Localization Strings

If you recall, way back in the beginning, when we were setting up our ClassDescriptions.lsx entries, we generated a handle for DisplayName and Description. We're going to use those to make sure our text is readable:

  1. In ClassDescriptions.lsx, copy your Class' DisplayName field's handle
  2. In that first content element we made in the boilerplate, pasting in your DisplayName field's handle into the contentuid.
  3. In between the opening and closing tags for your content element, input the name of your class.
  4. Repeat the above steps for the Description field to the second content element, inputting the description for your class.

Compiling the Localization

You're almost ready to test your Race again! Just one thing first: We need to convert our ModName.xml into a .loca file.

  1. Open LSLib and navigate to the Localization Tab
  2. Under Input file path, copy and paste the location and filename of your ModName.xml file
  3. Under Output file path, do the same, but replace .xml with .loca`
  4. Hit Convert

Note that this can all be done in the Community Library if you wish. You'll want to use CL_Classes.xml if it exists.

Now you just need to package your Implementation Mod, and test again. You should see the text you'd expect to see!

Abilities, Passives, and Spells

We have our class, and we can see what's going on, but what makes it stand out? What powers and resistances does it have? We need to set those up, so let's switch back to our Community Library branch. Navigate to Public/CommunityLibrary/Stats/Generated/Data. If you've checked out our guide to building a Race mod, you know this already, feel free to skip this section. The rest of you, let's take a look at the entry 1CL_MarchDead in CL_Passives.txt:

Passives

new entry "CL_MarchDead"
type "PassiveData"
data "DisplayName" "h613431bfg3cd7g4978g8c67g9d667817f3e4;4"
data "Description" "hc8b9a207gbb68g452cgaf74gbfb32c50c3b3;4"
data "Icon" "PassiveFeature_LandsStride_DifficultTerrain"
data "Properties" "Highlighted"
data "Boosts" "StatusImmunity(SG_DifficultTerrain)"

This is a fairly simple passive for the Death Knight class. We have localization handles for DisplayName and Description(we'll touch on that later), a defined Icon, properties set to Highlight the passive, and an effect granting Status Immunity to Difficult Terrain. Let's take a look at a slightly more complex one:

new entry "CL_UnholyWpn"
type "PassiveData"
data "DisplayName" "haecff2fbgf630g478cga6d5g0fe1e73b03e1;1"
data "Description" "hd22bba2egdc74g4118ga101g1176e4e6a351;1"
data "DescriptionParams" "DealDamage(WisdomModifier,Necrotic)"
data "Icon" "Mailem_DK_Unholyweapon"
data "Properties" "Highlighted"
data "StatsFunctorContext" "OnAttack"
data "Conditions" "not IsMiss() and IsMeleeWeaponAttack() "
data "StatsFunctors" "DealDamage(WisdomModifier,Necrotic,Magical)"

We can see a few interesting things going on here:

  1. DescriptionParams, this is information that is referenced in the Description Localization. Here we see that Necrotic Damage should be dealt based on the Wisdom Modifier. Sending the value and damage type to the localization string, where it is displayed in [1]. Additional information can be added in DescriptionParams with ; between each value. The order of how these are referenced is [1], [2], [3] and so on.

  2. We have some lines titles StatsFunctorContext, Conditions, and StatsFunctors. Let's get into those a bit.

Conditions and StatsFunctorContext

These lines identify the situations in which the Passive will trigger its StatsFunctors value. However, they both look at very different things. StatsFunctorContext identifies the specific event upon which the game should review the Conditions to determine if StatsFunctors fires. To put it plainly, StatsFunctorContext is the event, Conditions is the detail that must be true.

StatsFunctors

StatsFunctors are effectively a function call. This can be used to Apply a status, Deal Damage, or any number of other things. It will always rely on a StatsFunctorContext, and a Conditions line can help flesh it out further, but is optional.

Soon we'll look at applying these to our class, but for now, we'll move on to Spells and Abilities.

Spells/Abilities

If your class is a spellcasting class, you may want to define a few new spells. Or you may want some custom actions. If you only want to use already-existing spells and actions, feel free to skip ahead, otherwise, let's take a look at a spell:

new entry "CL_Target_WeaknessToFrost"
type "SpellData"
using "CL_Target_Weakness_BASE"
data "Level" "2"
data "Icon" "Status_Chilled"
data "DisplayName" "hf2534213g45afg49afg97d8g8a4c69077734"
data "Description" "hc98ee227g3b69g4792ga95eg8cc501160443"
data "SpellProperties" "ApplyStatus(CL_STATUS_WEAKNESS_TO_FROST, 100, 9)"
data "TooltipStatusApply" "ApplyStatus(CL_STATUS_WEAKNESS_TO_FROST, 100, 9)"

This is a fairly simple spell. From a quick read, we can tell quite a few things:

  1. It uses the data from a spell titled CL_Target_Weakness_BASE
  2. It's a level 2 spell
  3. It applies a status of CL_STATUS_WEAKNESS_TO_FROST.

Items

While we're here, let's take a look at adding new items. Maybe we want to go back and add a brand new item to the Class' equipment. Let's take a look at CL_Armor.txt:

new entry "DK_StarterHelmet"
type "Armor"
using "_Head_Magic_Metal"
data "RootTemplate" "8888628e-4eda-4eb5-bfb4-eb828782b051"
data "ValueUUID" "a229f048-70b0-4b0c-88cb-29b5c6bdb2d0"
data "Rarity" "Uncommon"
data "Boosts" "RollBonus(SavingThrow, 1, Constitution)"

We can see here that we're defining the type of item, using the data of a pre-existing item, _Head_Magic_Metal as a base, defining a Root Template, a Value UUID, a level of Rarity, and special bonuses for having the item equipped.

Passive and Spell Lists

This piece is held in the Community Library. Let's open up the Public/CommunityLibrary/Lists/ folder. Here there are two files: PassiveLists.lsx and SpellLists.lsx. Functionally, these lists are nearly identical. SpellList nodes have an optional, but useful, Comment attribute. Besides that, it works the same.

SpellList Nodes have the following attributes:

  • Comment - An optional field where you can place a description of what the node is for.
  • Spells - A semi-colon-separated list of Spells.
  • UUID - A UUID, which you'll have to generate when making new lists.

PassiveList Nodes have the following attributes:

  • Passives - A semi-colon separated list of Passives.
  • UUID - A UUID, which you'll have to generate when making new lists.

If you recall, in our Progressions section, we saw the Selectors attribute. This is where the Spell and Passive Lists come in to play. The UUIDs being referenced in those attributes map to these lists' UUID field. Once you've defined a Spell or Passive List, you'll want to go back to Progressions, and add these selectors via the AddSpells(), SelectSpells(), or SelectPassives() function calls.

You may notice we don't specify an AddPassives() function call - Progressions have an attribute you may remember, PassivesAdded - here you'll want to copy any passives you wish to grant for a given progression in a semi-colon separated list.

// TODO: Go into detail on extra parameters useable in Selectors.

Once you've set up your Class Progression's Selectors with your new Passive and/or Spell List information, package up your Community Library branch and your Implementation Mod, and test to see how your spells and passives are being granted.

Custom Equipment

Another Community Library Piece, the Custom Equipment file can be found near the Spells, Passives, and Statuses we went over earlier. Check in Public/CommunityLibrary/Stats/Generated/CL_Equipment.lsx. This is a DataItem, a type of file commonly used for Spells, Passives, Items, and Statuses. Equipment is a different type of object. Let's take a look at an Equipment entry:

new equipment "EQP_CC_DeathKnight"
add initialweaponset "Melee"
add equipmentgroup
add equipment entry "WPN_Necrosword"
add equipmentgroup
add equipment entry "OBJ_Potion_Healing"
add equipmentgroup
add equipment entry "OBJ_Potion_Healing"
add equipmentgroup
add equipment entry "ARM_Boots_Leather"
add equipmentgroup
add equipment entry "ARM_ChainMail_Body"
add equipmentgroup
add equipment entry "DK_StarterHelmet"
add equipmentgroup
add equipment entry "OBJ_Scroll_Revivify"
add equipmentgroup
add equipment entry "ARM_Camp_Body"
add equipmentgroup
add equipment entry "ARM_Camp_Shoes"
add equipmentgroup
add equipment entry "OBJ_Keychain"
add equipmentgroup
add equipment entry "OBJ_Bag_AlchemyPouch"
add equipmentgroup
add equipment entry "OBJ_Backpack_CampSupplies"

Overall, fairly simple stuff. It defines the initial loadout for the player, including the base weapon type, armor, scrolls, containers, and potions. Once you've defined your equipment, make sure to go back to your ClassDescription node and fill in the ClassEquipment attribute with your new equipment's entry name.

By this point, you should have just about everything you need for your custom class. The next three sections are window-dressing - great to have, but not entirely needed for a 1.0 release. Still, let's keep on going.

Class Icons

Note: The ImprovedUI Mod has recently updated and simplified the process of adding Custom icons. I'll update this guide with a summary of the link, however follow this link for details!

Skill Icons

// TODO

Handling Subclasses

Subclasses for a Custom Class

If you're trying to make a Custom Class with Subclasses, I've got some bad news for you: You'll need to repeat the above stages for your Subclass. You may already have guessed this, but it'll be a bit of effort. Regardless, whether you've finished that, or are just trying to add a Subclass to an existing Class, it's extremely easy to get things rolling.

You'll need to go to your Main Class' progression for the level at which your class will grant subclass options. If it doesn't already exist, add the following beneath the <attribute> tags:

<children>
    <node id="SubClasses">
        <children>

        </children>
    </node>
</children>

Now inside the interior <children> tags, paste the following:

        <node id="SubClass">
            <attribue id="Object" type="guid" value="your-subclass-class-description-uuid" />
        </node>

Paste the value as your Subclasses UUID as defined in ClassDescriptions.lsx.

Notes on Subclass Compatibility

This will be more of an issue if you're trying to add a subclass to an existing class, but good to be mindful of: Any mod that alters the same progression will conflict. Hard stop. There are ways around this:

  1. The Subclass Compatibility Framework - A project utilizing the Script Extender to insert any detected subclass mods at runtime.
  2. The BG3 Compatibility Patcher - A Python application that allows a user to patch together multiple mods affecting progressions. This doesn't always work as expected, but is a great option for those in a hurry.

If you'd like to rely on the patcher, you're good to go - compatibility will be up to users to set up. Otherwise, using the Compatibility Framework is a good approach, and the project maintains a guide on adding support for your mod.

⚠️ **GitHub.com Fallback** ⚠️