-
Notifications
You must be signed in to change notification settings - Fork 205
Building a Race Mod
One of the easiest ways to get into modding is create a custom race. Whether you have a cool homebrew concept, or there's an official race that Larian didn't add, there's a lot of room to play around in. This guide will walk you through the process of building a custom race.
Make sure to complete the steps outlined in the Getting Started page.
- Getting Started
- Race Entry
- Root Templates
- Character Visuals
- Character Creation
- Localization
- Racial Feats
- Racial Progressions
- Tags
- Custom Icons
First thing's first, we need to work on our Races.lsx
entry. Typically, this is something you'll want the Community Library to handle, as it's reusable and potentially useful for other modders. Community Library already has an existing Races.lsx
file, located in CommunityLibrary/Public/CommunityLibrary/Races/Races.lsx
, so that's where we'll start.
- Open up Community Library in VSCode(or your IDE of choice).
- Open up
CommunityLibrary/Public/CommunityLibrary/Races/Races.lsx
Here, we can see there are already a few entries in here. We can use these as examples, but it's worth taking a look at some of the default files, especially if there are any races similar to what you want to include. If you haven't already, unpack the Shared
and Gustav
.pak
files:
- Open Modder's Multitool
- Select "Unpack .pak Files
- Tick "Gustav.pak" and "Shared.pak."
- Select "Confirm," and wait for the process to complete.
Take a look at the Races.lsx
files unpacked form Gustav and/or Shared.pak, and identify a similar race. This will be important later.
Now, let's look at our copy again. What's important to us are the nodes with an ID of "Race". Here's the entry for Shadar-Kai.
<?xml version="1.0" encoding="UTF-8"?>
<save>
<version major="4" minor="0" revision="0" build="49"/>
<region id="Races">
<node id="root">
<children>
<node id="Race">
<attribute id="Description" type="TranslatedString" handle="h9a4e44c9g72e0g43b6ga72bgd83fcb6b40d1" version="1"/>
<attribute id="DisplayName" type="TranslatedString" handle="h6362370ag39d0g407fgaff0g3d98f3bb161e" version="1"/>
<attribute id="DisplayTypeUUID" type="guid" value="899d275e-9893-490a-9cd5-be856794929f"/>
<attribute id="Name" type="FixedString" value="CL_ShadarKai"/>
<attribute id="ParentGuid" type="guid" value="6c038dcb-7eb5-431d-84f8-cecfaf1c0c5a"/>
<attribute id="ProgressionTableUUID" type="guid" value="01735427-a4df-4ce4-8fb6-4b1b8c6c3771"/>
<attribute id="RaceSoundSwitch" type="FixedString" value="Elf"/>
<attribute id="UUID" type="guid" value="27844147-3e0f-4b2e-8377-8d015b8384df"/>
<children>
...
<children>
</node>
</children>
</node>
</region>
</save>
It's a lot to look at, I know. For now, ignore the <children>
section. We have a few attributes:
- Description: The value here refers to a localization string that defines the race's description.
- DisplayName: The value here refers to a localization string that defines the race's name, as seen ingame.
- DisplayTypeUUID: This will often be identical to the later
ParentGuid
attribute. Just keep these the same. - Name: This is the internal name of the Race - never seen ingame, but useful when referencing.
- ParentGuid: The Race this race inherits from. Base races will often inherit from the Humanoid race, while subraces may inherit from other races.
- ProgressionTableUUID: This references a specific progression table, defining spells, passives, and other key pieces. More on this later.
- RaceSoundSwitch: References the sound types made when switching characters.
- UUID: This is the Identifier of this race. It's how you'll reference the race when needed.
So what do we do with this? Let's start by copy/pasting it as a new Race node, and making our changes.
- Using Modder's Multitool, tick "Handle", and generate a new UUID.
- Copy the UUID(click on it's display), and paste it into
Description
'shandle
value. - Do the same for
DisplayName
. - Write an internal name, prefixed with CL_ to help ensure there aren't any collisions with other mods
- Set the RaceSoundSwitch based on another similar race.
- Untick "Handle" in Modder's Multitool, and generate a new UUID, pasting it into the
UUID
attribute'shandle
field.
- Find the race you want your race to be a subrace of
- Copy its UUID handle
- Paste it into your race's ParentGuid value.
Obviously, our race needs to have visuals, possibly even tags. You can opt not to have the <children>
tags, but if you want them to have features similar to another race, here's what to do:
- Find a similar race in the unpacked
Races.lsx
file. - Copy their
<children>
section over to your race. - Remove any
<node id="Tags">
sections UNLESS you want them to have the reference race's tags.
We can work with the Progression Table once we get to Progressions, leave it as is for now.
Root Templates define a lot about our races, and just about everything else. By default, they're in the .lsf
format, and will need to be converted into .lsx
before we can edit them. If you haven't already, let's do this to the unpacked RootTemplates files in Shared:
- In LSLib, navigate to the LSX/LSB/LSF/LSJ Tools tab.
- Under "Batch Convert," set the Input Directory to match Shared's RootTemplates directory (ex.
H:\BG3\ExportTool-v1.18.5\Multitool\UnpackedData\Shared\Public\Shared\RootTemplates
- Do the same for "Output Directory"
- Make sure Input format is set to LSF and Output format is set to LSX.
- Hit "Convert" and wait for the process to complete.
Now you can search, open, and edit the base Root Templates.
You may notice that the official Root Templates name looks awfully similar to a UUID, but the ones in Community Library aren't. The naming convention doesn't matter much here, so Community Library's policy is to keep RootTemplate names readable, to save everyone time.
When it comes to Root Templates, you'll typically have 6 per race: Base Female, Base Male, Female Player, Female Player Strong, Male Player, and Male Player Strong. You can create your Base root templates in Community Library, but the remaining 4 should go into your implementation mod. This will help keep things clean.
Your base templates will look something like this:
<?xml version="1.0" encoding="utf-8"?>
<save>
<version major="4" minor="0" revision="0" build="51" />
<region id="Templates">
<node id="Templates">
<children>
<node id="GameObjects">
<attribute id="MapKey" type="FixedString" value="27410506-9276-48e7-b4b4-38d861aa1be7" />
<attribute id="Name" type="LSString" value="BASE_Elves_Female_ShadarKai" />
<attribute id="LevelName" type="FixedString" value="" />
<attribute id="Type" type="FixedString" value="character" />
<attribute id="ParentTemplateId" type="FixedString" value="4c3d8aa0-2e31-4dce-a792-84fc07fa63d8" />
<attribute id="VisualTemplate" type="FixedString" value="" />
<attribute id="Icon" type="FixedString" value="24bcc44f-4168-1468-222a-b1bb6370e000-_(Icon_Elf_Female)" />
<attribute id="CharacterVisualResourceID" type="FixedString" value="f89e3922-f92e-4279-809e-4a95336a75a9" />
<attribute id="Race" type="guid" value="27844147-3e0f-4b2e-8377-8d015b8384df" />
<children>
<node id="GameMaster" />
<node id="LocomotionParams" />
</children>
</node>
</children>
</node>
</region>
</save>
Once again, a lot to look at, so I'll summarize:
- MapKey: The Identifier for your Template. This corresponds with the filenames for the default root templates. This is what you'll use to reference a template elsewhere.
- Name: The internal name of the Template. This is what Community Library names the Template files.
- LevelName: Typically blank, but possibly needs to exist.
- Type: For races, this will always be "character".
- ParentTemplateID: This will be the ID of the Template our Template inherits from.
- VisualTemplate: For races, this will typically be blank.
- Icon: A string that consists of a Race's ID and icon text.
- CharacterVisualResourceID: A reference to the Visuals files for our race.
- Race: This will be the UUID field of your Race entry in
Races.lsx
.
Create two new Base Root Templates for your Race in Community Library's RootTemplates
folder, filling in what you can. You'll want to skip CharacterVisualResourceID for now. We'll get back to that one soon enough.
Next, we'll take a look at the implemented versions. These, you'll be setting up in your Implementation mod. Let's look at my implemented ShadarKai player first:
<?xml version="1.0" encoding="utf-8"?>
<save>
<version major="4" minor="0" revision="0" build="51" />
<region id="Templates">
<node id="Templates">
<children>
<node id="GameObjects">
<attribute id="MapKey" type="FixedString" value="692bd578-f4fe-43b1-be34-dfcfef091fe8" />
<attribute id="Name" type="LSString" value="Elves_Female_ShadarKai_Player" />
<attribute id="LevelName" type="FixedString" value="" />
<attribute id="Type" type="FixedString" value="character" />
<attribute id="ParentTemplateId" type="FixedString" value="27410506-9276-48e7-b4b4-38d861aa1be7" />
<attribute id="Flag" type="int32" value="0" />
<attribute id="Icon" type="FixedString" value="d67bd924-3c1f-c33a-5298-feca5bbdc284-_(Icon_Elf_Female)" />
<attribute id="Stats" type="FixedString" value="HeroElfFemale" />
<attribute id="SpellSet" type="FixedString" value="CommonPlayerActions" />
<attribute id="CharacterVisualResourceID" type="FixedString" value="4358bd61-aade-4eab-b6b5-eb66c48aced0" />
<attribute id="PortraitVisualResourceID" type="FixedString" value="e1254a4e-3d52-76c2-7b19-52e93941b6dd" />
<children>
<node id="SpeakerGroupList">
<children>
<node id="SpeakerGroup">
<attribute id="Object" type="guid" value="02cdc15e-1dc3-5eb4-3cf7-5894f9b49f0b" />
</node>
<node id="SpeakerGroup">
<attribute id="Object" type="guid" value="e0d1ff71-04a8-4340-64ae-849646d883eb" />
</node>
</children>
</node>
<node id="GameMaster" />
<node id="LocomotionParams" />
</children>
</node>
</children>
</node>
</region>
</save>
Looks a bit familiar, but it has a few additional fields:
- Flag: Typically 0.
- Stats: References a Data field. We'll get to these later.
- SpellSet: typically CommonPlayerActions for playable races.
- PortraitVisualResourceID: Corresponds to a visual for the character's portrait.
It also has a few new SpeakerGroup
fields as children. These will typically be the same across all playable races, and map to the feminine and masculine voices.
The Parent template references out Base template for the corresponding sex, and the name is more specific, but otherwise, not too much new.
Finally, let's look at the Strong version, for buff bodies:
<?xml version="1.0" encoding="utf-8"?>
<save>
<version major="4" minor="0" revision="0" build="51" />
<region id="Templates">
<node id="Templates">
<children>
<node id="GameObjects">
<attribute id="MapKey" type="FixedString" value="8bd25683-8607-48b4-a7f8-e9f1fecba74a" />
<attribute id="Name" type="LSString" value="Elves_Female_ShadarKai_Player_Strong" />
<attribute id="LevelName" type="FixedString" value="" />
<attribute id="Type" type="FixedString" value="character" />
<attribute id="ParentTemplateId" type="FixedString" value="692bd578-f4fe-43b1-be34-dfcfef091fe8" />
<attribute id="Icon" type="FixedString" value="eb370ddf-f7a4-4349-116e-f489556bab1d-_(Icon_Human_Female_Strong)" />
<attribute id="EquipmentRace" type="guid" value="47c0315c-7dc6-4862-9bb3-f38b0fa1548b" />
<attribute id="CharacterVisualResourceID" type="FixedString" value="cd4bceed-4cbb-4e9a-8531-c08bbb857ccf" />
<attribute id="GeneratePortrait" type="LSString" value="Icon_Human_Female_Strong" />
<children>
<node id="Tags">
<children>
<node id="Tag">
<attribute id="Object" type="guid" value="d3116e58-c55a-4853-00a7-e9be20969773" />
</node>
</children>
</node>
<node id="GameMaster" />
<node id="LocomotionParams" />
</children>
</node>
</children>
</node>
</region>
</save>
Three important new things:
- EquipmentRace: This defines the equipment to switch to now that we're using buff bodies. Without it, there'll be a ton of clipping.
- GeneratePortrait: This maps to an icon, and for Strong character types, will often reference Humans rather than other races.
- Tags: This sets the Swole* tag on your character when selecting the strong bodies.
*Note: It's not actually called Swole.
By putting your Base root templates into Community Library, other modders can utilize it for situations where they want to add an NPC using your added race. Rather than duplicating the effort of having their own unique Base template, they can build off of yours, and you can build off of theirs. It's also one less file you have to worry about maintaining directly.
With this information, play around to your hearts content. We'll dive into Visuals next, but first, once you're done editing your Root Templates, you'll want to convert them to .lsf
:
- In LSLib, navigate to the LSX/LSB/LSF/LSJ Tools tab.
- Under "Batch Convert," set the Input Directory to match CommunityLibrary's RootTemplates directory.
- Do the same for "Output Directory."
- Make sure Input format is set to LSX and Output format is set to LSF.
- Hit "Convert" and wait for the process to complete.
- Do the same for your Implementation Mod's RootTemplates directory.
This will look similar to Root Templates. These are also .lsf
files that you'll want to convert to .lsx
, and they can be found in the Public/ModName/Content/[PAK]_CharacterVisuals/._merged.lsf
file, all bundled together. This goes for Shared and Gustav, as well, if you want to get a sense of things from the official files. There will be 6 total "Resource" nodes you'll want: 2 Within Community Library, and 4 within your Implementation Mod.
Once you've converted and opened _merged.lsf, you'll see something like this:
<?xml version="1.0" encoding="utf-8"?>
<save>
<version major="4" minor="0" revision="0" build="51" />
<region id="CharacterVisualBank">
<node id="CharacterVisualBank">
<children>
<node id="Resource">
<attribute id="BaseVisual" type="FixedString" value="4deb74a3-fe9a-723f-89b5-799a97335cc4" />
<attribute id="BodySetVisual" type="FixedString" value="017534b5-2b56-4160-9d8c-7712d3a413ea" />
<attribute id="ID" type="FixedString" value="b8e49e88-a0bd-480d-9e8b-8a24a4444376" />
<attribute id="Localized" type="bool" value="False" />
<attribute id="Name" type="LSString" value="BASE_Elves_Male_ShadarKai" />
<attribute id="ShowEquipmentVisuals" type="bool" value="False" />
<attribute id="_OriginalFileVersion_" type="int64" value="144115205255725056" />
<children>
<node id="MaterialOverrides">
<attribute id="MaterialResource" type="FixedString" value="" />
<children>
<node id="ColorPreset">
<attribute id="ForcePresetValues" type="bool" value="True" />
<attribute id="GroupName" type="FixedString" value="" />
<attribute id="MaterialPresetResource" type="FixedString" value="" />
</node>
<node id="MaterialPresets">
<children>
<node id="Object">
<attribute id="ForcePresetValues" type="bool" value="True" />
<attribute id="GroupName" type="FixedString" value="02Skin Properties" />
<attribute id="MapKey" type="FixedString" value="02Skin Properties" />
<attribute id="MaterialPresetResource" type="FixedString" value="ace8e94a-4ec0-45b3-6ff0-46a6a4a008ed" />
</node>
</children>
</node>
</children>
</node>
<node id="Slots">
<attribute id="Slot" type="FixedString" value="Head" />
<attribute id="VisualResource" type="FixedString" value="c1c97ff6-04b6-37c0-b855-681b18447fc4" />
</node>
<node id="Slots">
<attribute id="Slot" type="FixedString" value="Body" />
<attribute id="VisualResource" type="FixedString" value="123ff6f0-f424-61f1-1881-2cefaaf5f8ec" />
</node>
<node id="Slots">
<attribute id="Slot" type="FixedString" value="Body" />
<attribute id="VisualResource" type="FixedString" value="7a0fbf45-3619-b7f9-f405-2fd48bfd08cb" />
</node>
<node id="Slots">
<attribute id="Slot" type="FixedString" value="Footwear" />
<attribute id="VisualResource" type="FixedString" value="2627cae6-0565-7a31-466c-2ef8c283d9e3" />
</node>
<node id="Slots">
<attribute id="Slot" type="FixedString" value="Hair" />
<attribute id="VisualResource" type="FixedString" value="b801b19c-a373-0c0b-0a8c-30758b460178" />
</node>
</children>
</node>
</children>
</node>
</region>
</save>
It may have more or different children fields. In the case of the above, this is what a base visual will look like. Let's take a look at what we have:
- BaseVisual: The ID of whichever visual this inherits from.
- BodySetVisual: The ID of the BodySetVisual this inherits from.
- ID: As with everything else, this is how the resource node is referenced elsewhere.
- Localized: Whether or not any values are localized. This will typically be false here.
- Name: Just like with the Root Template, this is the internal name for the Resource node
- ShowEquipmentVisuals: Covers whether or not Equipment Visuals will be shown. For base visuals, this is typically false.
- _OriginalFileVersion: More for posterity than anything else, this defines the version of the file. Safest to keep it the same.
- Material Overrides
- Color Presets: Optional reference to a Material Preset Resource governing colors
- Material Presets: Optional reference to a Material Preset. For Base classes, this will typically just cover Skin Properties
- Slots: There will be several of these, typically defining Visual resources for specific parts of the body
With that information, and the above example, you should be ready to tackle your own Base Visuals! Once you've played around with it, let's take a look at what the more detailed, implemented versions of visuals look like:
Just as with Root Templates, the base Race Visual Resources have been defined in Community Library. For your custom race to become useable, you'll need Resources for the Player to use. These will be set up in your implementation mod. These have a tendency to be huge, so I won't copy/paste in a full example, but these are the attributes you can expect. Many of these will be very familiar:
- BaseVisual: This will typically reference the ID of your above Base visual
- BodySetVisual
- ID
- Name
- ShowEquipmentVisuals
- _OriginalFileVersion
- MaterialOverrides
- MaterialPresets
- ScalarParameters
- Vector3Parameters
- Slots
We saw most of these in the Base Visual resource earlier, but the ScalarParameters and Vector3Parameters are new. These would look something like this:
<node id="ScalarParameters">
<attribute id="Color" type="bool" value="False" />
<attribute id="Custom" type="bool" value="True" />
<attribute id="Enabled" type="bool" value="False" />
<attribute id="Parameter" type="FixedString" value="doBodyHide" />
<attribute id="Value" type="float" value="1" />
</node>
<node id="Vector3Parameters">
<attribute id="Color" type="bool" value="False" />
<attribute id="Custom" type="bool" value="True" />
<attribute id="Enabled" type="bool" value="False" />
<attribute id="Parameter" type="FixedString" value="AddedColor" />
<attribute id="Value" type="fvec3" value="0.02570003 0 0" />
</node>
There are any number of these per resource, and they're identified by their Parameter fields. A non-exhaustive list is as follows:
Scalar Parameters:
DetailNormalStrength, Anisotropy, Reflectance, Opacity, Roughness, PixelDepthOffsetRoot, PixelDepthOffset, DepthTransitionMidPoint,
DepthTransitionSoftness, ColorTransitionMidPoint, ColorTransitionSoftness, IDContrast, ColorDepthContrast, UseOcclusion, Scalp_Scatter,
Scalp_ColorTransitionMidPoint, Scalp_ColorTransitionSoftness, Scalp_DepthColorExponent, Scalp_DepthColorIntensity, Scalp_IDContrast,
Scalp_ColorDepthContrast, Scalp_Roughness, Scalp_RoughnessContrast, doDrawMakeup, doBodyHide
Vector3Parameters:
AddedColor, Color_01, Color_02, Color_03
Once we've got our Visuals defined, we'll want to go back to our Root Templates, and set our CharacterVisualResourceID
fields to the MapKey of the relevant visual. Then, we'll want to convert our _merged.lsx
files back into _merged.lsf
. Finally, we're ready to move on to getting the Race selectable.
To recap what we've done so far:
- We've created our mod structure for the implementation mod, and prepared a local branch on the Community Library
- We created a Race entry inside the Community Library's
Races.lsx
file - We've defined Root Templates within Community Library for the base racial template, and within our Implementation mod for the Player racial templates.
- We've also defined Visual Resources within Community Library for the base racial visuals, and within our Implementation mod for the Player racial visuals.
We're about halfway through the process now. The next, and arguably most important step, is to ensure our Race is playable. For this step, we'll only be working with our Implementation Mod:
- In
Public/ModName
, create a subfolder titledCharacterCreationPresets
. - Under
CharacterCreationPresets
, create a file titlesCharacterCreationPresets.lsx
.
This file covers the main presets for choosing a character of a specific race. Examples can be found in Shared
on how these look, but typically for one race, you'll have 4 presets - one each for Feminine, Masculine, Feminine Strong, and Masculine Strong. It'll look something like this, only with four CharacterCreationPreset
nodes:
<?xml version="1.0" encoding="UTF-8"?>
<save>
<version major="4" minor="0" revision="0" build="47"/>
<region id="CharacterCreationPresets">
<node id="root">
<children>
<node id="CharacterCreationPreset">
<attribute id="BodyShape" type="uint8" value="0"/>
<attribute id="BodyType" type="uint8" value="1"/>
<attribute id="CloseUpA" type="LSString" value="ELF_F_Camera_Closeup_A"/>
<attribute id="CloseUpB" type="LSString" value="ELF_F_Camera_Closeup_B"/>
<attribute id="Overview" type="LSString" value="ELF_F_Camera_Overview_A"/>
<attribute id="RaceUUID" type="guid" value="6c038dcb-7eb5-431d-84f8-cecfaf1c0c5a"/>
<attribute id="RootTemplate" type="guid" value="692bd578-f4fe-43b1-be34-dfcfef091fe8"/>
<attribute id="SubRaceUUID" type="guid" value="27844147-3e0f-4b2e-8377-8d015b8384df"/>
<attribute id="UUID" type="guid" value="014a6ff2-a134-46a0-b587-2186727f1dc1"/>
<attribute id="VOLinesTableUUID" type="guid" value="14df8f45-90af-4bd0-8024-42624da9976e"/>
</node>
</children>
</node>
</region>
</save>
Overall, it's fairly simple. To break down each attribute, we have:
- BodyShape: 0 = Regular, 1 = Strong
- Body Type: 0 = Masculine, 1 = Feminine
- CloseUpA: The Camera viewpoint for closeups (A) - typically you'll want something that matches an existing race with a similar stature to your race
- CloseUpB: Similar to the above, but a different Closeup value
- Overview: The Camera viewpoint for the zoomed out view in CC. Overall, similar to the above
- RaceUUID: This will be the UUID of the Race you created at the beginning of the guide
- RootTemplate: The Relevant Implementation Root Template for the Body Type and Shape
- SubRaceUUID: The relevant UUID for a Sub-Race, if you made subraces.
- UUID: The identifier for this preset. Generate a new UUID for it.
- VOLinesTableUUID: References the Voiceover Lines Table. Typically for a playable character, this will be
14df8f45-90af-4bd0-8024-42624da9976e
Easy, right? Well, we're not done yet, but let's pack up the Implementation Mod and our modified Community Library, and see how it all looks in-game so far. 1.
You may have noticed that, while your race shows up, there's a lot of "Not Found." in the name and description. We created a new handle for display names and descriptions, but we never actually mapped those handles to anything. We'll need to do that next. This will take place entirely within Community Library.
In your Community Library branch, open Localization/English/CL_Races.xml
It'll look something like this:
<contentList date="8/21/2023 5:00">
<content contentuid="h9a4e44c9g72e0g43b6ga72bgd83fcb6b40d1" version="1">Text</content>
<content contentuid="h7365c7bega8e7g4746g9f64g50548db16e70" version="1">Text</content>
<content contentuid="h7901179eg9a5ag4a33gb561g10acfe80a85c" version="1">Text</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" ingame.
If you recall, way back in the beginning, when we were setting up our Races.lsx
entries, we generated a handle for DisplayName
and Description
. We're going to use those to make sure our text is readable:
- In
Races.lsx
, copy your Race'sDisplayName
field's handle - Add a new
content
element, pasting in yourDisplayName
field's handle intocontentuid
. - In between the opening and closing tags for your
content
element, input the name of your race. - Repeat the above steps for the
Description
field, inputting the description for your race.
You're almost ready to test your Race again! Just one thing first: We need to convert our CL_Races.xml
into a .loca
file.
- Open LSLib and navigate to the Localization Tab
- Under
Input file path
, copy and paste the location and filename of yourCL_Races.xml
file - Under
Output file path
, do the same, but replace.xml
with .loca` - Hit Convert
Now you just need to package your local version of community library, and test again. You should see the text you'd expect to see!
We've got our race ready, but something feels off. There's nothing special about the race, nothing that makes it unique or interesting. This is where Feats and Progressions come in! First, we'll start with Feats. You can do this within Community Library, or within your Implementation mod. There are a few examples of what these look like within Community Library, all within Public/ModName/Stats/Generated/Data/
:
In your Community Library branch, open CL_Passives.txt
, and take a look at CL_Passive_RavenQueenBlessing`.
new entry "CL_Passive_RavenQueenBlessing"
type "PassiveData"
data "DisplayName" "h5bcb3c31gc511g4bc3gbf7bgde64d29ab296"
data "Description" "h6dc6d54fgbbdeg4b6cgadc8g3922b34328e3"
data "Conditions" "SpellId('CL_Target_RavenQueenBlessing')"
data "StatsFunctors" "ApplyStatus(CL_STATUS_RAVEN_QUEEN_BLESSING, 100, 1)"
data "StatsFunctorContext" "OnSpellCast"
This is a passive, with a DisplayName
and Description
handle. It has a Conditions
field targetting a specific Spell ID. and StatsFunctors
, which is a function applied based on the above defined conditions, as well as the StatsFunctorContext
beneath. In this example, when the spell CL_Target_RavenQueenBlessing is cast, the caster receives the status CL_STATUS_RAVEN_QUEEN_BLESSING, with 100% accuracy, for 1 turn.
Passives can do a variety of things, and they don't always require something to happen first. You'll also notice near the top, this entry:
new entry "CL_Passive_Petpal"
type "PassiveData"
data "DisplayName" "he617ad50g9705g4074gb9a5ge11b1fb04aec"
data "Description" "haa2a6bafg712dg477egb241gbcdcd360b364"
data "Boosts" "ApplyStatus(PETPAL,100,-1)"
Boosts
defines a bonus applied to the character with the passive. In this case, it permanently applies the status of PETPAL
, allowing a permanent Talk to Animals effect. A boost can confer statuses, resistances, vulnerabilities, immunities, and more.
Open CL_Spells_Target.txt
. Look for the entry CL_Target_RavenQueenBlessing
.
new entry "CL_Target_RavenQueenBlessing"
type "SpellData"
data "SpellType" "Target"
data "SpellProperties" "GROUND:TeleportSource();"
data "TargetRadius" "9"
data "AreaRadius" "1"
data "Height" "0"
data "Acceleration" "0"
data "TooltipUseCosts" "BonusActionPoint:1;RQBlessing:1"
data "TargetConditions" "CanStand('') and not Character() and not Self()"
data "TeleportSelf" "Yes"
data "TeleportSurface" "No"
data "Icon" "Action_Ranger_RangersCompanion_Raven"
data "DisplayName" "he699f74ag1af1g48d3gbbf9g19d38c41bab6"
data "Description" "h234b5017g929bg4403g9031gdc09713fbb1a"
data "UseCosts" "BonusActionPoint:1;RQBlessing:1"
data "PreviewCursor" "Cast"
data "CastTextEvent" "Cast"
data "PrepareEffect" "7121a488-7c9a-4ba1-a585-f79aaa77e97c"
data "SpellAnimation" "dd86aa43-8189-4d9f-9a5c-454b5fe4a197,,;,,;39daf365-ec06-49a8-81f3-9032640699d7,,;5c400e93-0266-499c-a2e1-75d53358460f,,;cc5b0caf-3ed1-4711-a50d-11dc3f1fdc6a,,;,,;1715b877-4512-472e-9bd0-fd568a112e90,,;,,;,,"
We have quite a bit more to look at here, so first I'll explain what the spell does, then I'll break it down. This spell allows you to target a specific location within 9m, and teleport to it. It costs a Bonus Action, as well as a resource, RQBlessing. It can't be cast on self or on a Character, but it teleport's the player.
-
SpellType
: References the type of spell we're casting. This can be Teleport, Shout, Target, or others. More detail can be found in Shared, SharedDev, Gustav, and GustavDev. - SpellProperties: Defines the specific properties of the spell.
- TargetRadius: Defines the total radius of the spell around the caster.
- AreaRadius: Defines the total radius of the location the spell is cast.
- Height: Defines the possible height of the spell.
- Acceleration: Defines the speed of the player going through the spell.
- UseCosts: The Cost of the Spell
- TooltipUseCosts: Defines the Cost of the Spell as shown in its tooltip
- TargetConditions: The conditions to be able to cast on the target.
The rest aren't particularly important to know 100% of what they do, but you'll want to be sure that your spell/action has a SpellAnimation
field, otherwise the spell may become impossible to cast. I recommend finding a spell with a similar animation for casting and preparing, and then using its SpellAnimation
field.
I would recommend looking through the spells and passives in the Shared
, SharedDev
, Gustav
, and GustavDev
folders to get a sense of what it possible.
Regardless of what you're adding, or where you're adding it, you'll need to add some localization lines. You can either create a localization .xml
file similar to the one you found in Community Library, if making it in your Implementation Mod, or you can add it to a relevant or new localization .xml
in your Community Library branch. You'll need one for each Description field and DisplayName field you use.
If your spell/action includes a DescriptionParams
field, you'll want to end your Description
field in the Spell definition with ;#
, replacing #
with the amount of Description Parameters defined. Then, in your Localization String in your Localization .xml
file, to reference those parameters, you'll input [#]
, with #
being replaced with the number that represents your desired Description Parameter.
Finally, you'll save and compile the localization files, as you learned above.
So, we've got these cool feats, but how do we add them to our race? This is where Spell Lists and Progressions come in. Like the above section, you can keep these within your Implementation mod, or you can set them up in Community Library. We'll take a look at some examples within Community Library.
Let's check out our Spell Lists - Open up Public/ModName/Lists/SpellLists.lsx
. You'll see something like this:
<?xml version="1.0" encoding="UTF-8"?>
<save>
<version major="4" minor="0" revision="0" build="49"/>
<region id="SpellLists">
<node id="root">
<children>
<node id="SpellList">
<attribute id="Comment" type="LSString" value="Raven Queen's Blessing Ability"/>
<attribute id="Spells" type="LSString" value="CL_Target_RavenQueenBlessing"/>
<attribute id="UUID" type="guid" value="cdea5ca8-e43f-4830-a265-76fec11ab79a"/>
</node>
</children>
</node>
</region>
</save>
As you can see, these SpellList
nodes are pretty small. You have a Comment
attribute, which is just a descriptive string on what the spells are for. You have a Spells
attribute, which is a semi-colon-separated list of Spell IDs (Important to note that you don't add Passives here). Finally, you have a UUID
attribute, which is yet another UUID to generate.
So now you have this SpellList, or maybe you didn't add any spells, but want to know what to do with your passives. Like the above section, you can define these in either your Implementation Mod or Community Library. For an example of what this might look like, open any of the .lsx
files under the Progressions folder. You'll see something like this:
<?xml version="1.0" encoding="UTF-8"?>
<save>
<version major="4" minor="0" revision="0" build="49"/>
<region id="Progressions">
<node id="root">
<children>
<node id="Progression">
<attribute id="Boosts" type="LSString" value="Ability(Constitution,1);ActionResource(RQBlessing,2,0)"/>
<attribute id="Level" type="uint8" value="1"/>
<attribute id="Name" type="LSString" value="CL_ShadarKai"/>
<attribute id="PassivesAdded" type="LSString" value="CL_Passive_NecroticResistance;CL_KeenSenses"/>
<attribute id="ProgressionType" type="uint8" value="2"/>
<attribute id="Selectors" type="LSString" value="AddSpells(cdea5ca8-e43f-4830-a265-76fec11ab79a,,,,AlwaysPrepared)"/>
<attribute id="TableUUID" type="guid" value="01735427-a4df-4ce4-8fb6-4b1b8c6c3771"/>
<attribute id="UUID" type="guid" value="2f7edf7e-0a6b-4018-9715-1cb8aa238e4a"/>
</node>
<node id="Progression">
<attribute id="Level" type="uint8" value="3"/>
<attribute id="Name" type="LSString" value="CL_ShadarKai"/>
<attribute id="PassivesAdded" type="LSString" value="CL_Passive_RavenQueenBlessing"/>
<attribute id="ProgressionType" type="uint8" value="2"/>
<attribute id="Selectors" type="LSString" value=""/>
<attribute id="TableUUID" type="guid" value="01735427-a4df-4ce4-8fb6-4b1b8c6c3771"/>
<attribute id="UUID" type="guid" value="f2d55a86-433b-4f79-b381-7510cafef385"/>
</node>
</children>
</node>
</region>
</save>
It's fairly simple, but let's break this down. In each node, we have:
- Boosts: A semicolon-separated list of values, typically Ability bonuses or action resources granted to the character.
- Level: The level at which the progression takes effect
- Name: An internal identifier
- PassivesAdded: A semicolon-separated list of passive IDs
- ProgressionType
- Selectors: A semicolon-separated list of functions, granting particular Options. If using spells, you'll see
AddSpells
, which takes in the UUID of a SpellList node. There are other options as well, such asSetSkills
. To see more, check throughShared
,SharedDev
,Gustav
, orGustavDev
. - TableUUID: The UUID that represents a set of Progressions
- UUID: The UUID representing the specific progression
The above two nodes do the following:
- At level 1, the character gains +1 to their Consititution score, as well as 2 RQBlessing Action Resources. They also receive the Necrotic Resistance and Keen Senses passives, along with a spell that's always prepared, defined in a specific SpellList.
- At level 3, the character gains the Raven Queen's Blessing passive.
All that's left to make this character playable is to go back to the beginning. In Races.lsx
, where we defined the race, we need to go take a look at our race's ProgressionTableUUID
. Here, we'll want to copy/paste our TableUUID
from our Progressions nodes. Once that's done, we can repack the mod, then jump in with a fully-featured custom race!
Everything currently looks great in Character Creation, but if you started a game with the character and looked at your Character Sheet, you might have noticed a couple issues:
- Your Race doesn't show up at all
- There are no tags outside of the Baldurian tag.
This could be an issue. After all, Tags are where the majority of the game's content becomes available. Dialog paths and interactions rely heavily on what tags the player has available to them. These are typically saved as .lsf
files, so you'll need to make sure you compile yours with LSLib once you're done setting them up. But first, let's set up a tag:
- Navigate to
CommunityLibrary/Public/CommunityLibrary/
. - Create a
Tags
folder. - Generate a UUID and copy it.
- Within the
Tags
folder, create an.lsx
file, and paste your UUID as the filename.
Now, let's open our Tag up, and paste in some Boilerplate:
<?xml version="1.0" encoding="utf-8"?>
<save>
<version major="4" minor="0" revision="0" build="58" />
<region id="Tags">
</region>
</save>
Great, now that we have that, we're ready to take a look at the contents of a tag file:
<node id="Tags">
<attribute id="Description" type="LSString" value="|Shadar-Kai|" />
<attribute id="DisplayDescription" type="TranslatedString" handle="h38c61b04g98b8g4cc4g8cfcga1b205b26d13" version="1" />
<attribute id="DisplayName" type="TranslatedString" handle="h976d56ddgba92g413agbd32gd85dd2551d8f" version="2" />
<attribute id="Icon" type="FixedString" value="" />
<attribute id="Name" type="FixedString" value="CL_ShadarKai" />
<attribute id="UUID" type="guid" value="18e053a8-3887-4ffa-1593-128e7285a4e4" />
<children>
<node id="Categories">
<children>
<node id="Category">
<attribute id="Name" type="LSString" value="Code" />
</node>
<node id="Category">
<attribute id="Name" type="LSString" value="Dialog" />
</node>
<node id="Category">
<attribute id="Name" type="LSString" value="Race" />
</node>
<node id="Category">
<attribute id="Name" type="LSString" value="PlayerRace" />
</node>
<node id="Category">
<attribute id="Name" type="LSString" value="CharacterSheet" />
</node>
</children>
</node>
</children>
</node>
Here, we can see that a tag is made up of small amount of attributes, and has multiple categories it can be a part of. This tag in particular is setting up the tag containing the race's name and description.
- Description: This will be a string, sometimes formatted with a pipe symbol. It should be representative of your Tag's purpose.
- DisplayDescription: This is a localization handle corresponding to the desired description of the race. Typically this will be shorter than the description shown in Character Creation, but you can reuse the handle used for your race if you'd like.
- DisplayName: Like the above, this is a localization handle corresponding to the name of your race.
- Icon: For a race, this will typically be blank.
- Name: This is the internal string reference to your Tag.
- UUID: This is an ID for your tag. However, this is not how you'll reference the tag in other files - you'll instead want to use its Filename, which is a different UUID.
In addition to the above fields, there's also a significant amount of Categories listed in this example, under the Children element. These all define where the tag can be expected to be used.
Once you've created your tag, compile it into a .lsf file using LSLib.
Now that we have our tag, let's go into our Races.lsx
file. Under your Race node's children
element, paste the following, changing the value to the filename of your tag:
<node id="Tags">
<attribute id="Object" type="guid" value="18e053a8-3887-4ffa-9315-8e188572e4a4"/>
</node>
While you're here, copy your Race's UUID. You'll need it for the next step.
Now we're going to take another look at your Root Templates. Whichever Root Templates you've added that have a Race
field, you'll want to open and perform these actions on:
- Paste the value you came up with into the value field of your Race Attribute.
- Paste the following into your Template's
children
element, replacing the guid value with your tag's filename:
<node id="Tags">
<children>
<node id="Tag">
<attribute id="Object" type="guid" value="18e053a8-3887-4ffa-9315-8e188572e4a4" />
</node>
</children>
</node>
Again, be sure to do this for EVERY Root Template you've defined for your Race. Once you've done this, you're pretty much finished with the mod, unless...
WIP Notes:
As of September 2nd 2023, The ImprovedUI Mod has updated and simplified the process of adding Custom icons. This part of the guide will be updated eventually.
Now that you've got everything set up, there's one thing that might be bothering you.
It exists, that's for sure. It might even be fine as is. But if you want something unique, something that draws the player's attention and really says "This is my custom race, right here," this section is for you.
I'm here to tell you that it's quite possible, with a bit of legwork. If you don't have an icon in mind yet, that's fine. Come back to this step when you've got something you want. This guide won't walk you through the artistic creation of the icon, although you'll want to make sure of a few things:
- Make sure your desired icon is 196x196.\
- Use the naming scheme
Race_Subrace.DDS
(for example,Elf_ShadarKai.DDS
) - Save your copy in
.DDS
format (all caps).
Now you're ready to plug it in. To do this, you'll need to create a fork of BG3 ImprovedUI.
- Fork the Repository
- Clone your forked repository to your PC and make a new branch: `git checkout -b "MyRaceName-icons" 3.open it up in VSCode.
- Navigate to or create the directory
ImprovedUI/Public/Game/GUI/Assets/CC/icons_races
- Drop your race icons into the
icons_races
folder.
It's time to edit the .xaml files.
- Navigate to ImprovedUI/Public/Game/GUI/Assets/Library folder
- Open the file
DataTemplates.xaml
3 Look for theDataTrigger
elements referencing Racial icons. It will look something like this:
<DataTrigger Binding="{Binding Guid}" Value="a459ba68-a9ec-4c8e-b127-602615f5b4c0">
<Setter Property="OpacityMask">
<Setter.Value>
<ImageBrush ImageSource="pack://application:,,,/GustavNoesisGUI;component/Assets/CC/icons_races/Elf_WoodElf.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
- If making a subrace, find the
DataTrigger
that is nearest to your subraces main race, otherwise go to the last of the Race-relatedDataTriggers
. - Copy and Paste the
DataTrigger
and its child elements beneath the one you found. - Edit the
component/Assets/CC/icons_races/filename.png
to match your race icon's filename. - Save the file
So we're ready to make a Pull Request and make this official, right? Not quite. First, let's test the icon - make sure it looks like how you want it.
- Open LSLib's ConverterApp
- Navigate to PAK/LSV Tools
- Under "Create Package," set the Priority field to 21
- Also make sure the version is V18 (Baldur's Gate 3 Release)
- Enter your Source Path and expected Package Path for your local version of ImprovedUI and hit "Create Package."
- Import your new
ImprovedUI.pak
into your load order, save it, and then load the game. - See if it looks good, and make any adjustments if you think it needs changing.
Now it's time to submit this to ImprovedUI. Don't worry, most of this will just involve waiting for it to be accepted.
- Using Gitbash, add the changed and added files to your commit:
git add ImprovedUI/Public/Game/GUI
- use
git commit
to make your commit. -
use
git push --set-upstream origin BRANCHNAME`, subbing BRANCHNAME for the branch name we set up earlier in this section. - Open your forked repo on github. There'll be a little notification that looks something like this:
- Select "Contribute" and proceed through the steps to make a Pull Request.
We're technically done, but this last step will be good to have, just in case. At the start of the section, we dropped our icons into ImprovedUI's icons_races
folder. We're going to want to do that for our mod, as well.
Now, because Community Library doesn't cover Character Creation bits and pieces, there's no need to submit a Pull Request for the icons - you'll want to keep them with your implemented mod.
- In your ModName/Public, folder, create the following folder structure:
Game\GUI\Assets\CC\icons_races
- Drop your racial icons into
icons_races
.
Now you're good to go. I recommend doing one last test, and when you're sure it works, submit it to Nexus!
- Overview
- Sample Projects
- Action Resources
- Spells
- Passives
- Statuses
- Items
- Races
- Classes
- Scripts
- Visual Resources
Setting up a Development Environment (BEING REWRITTEN)
- A Modder's Guide to Git
- Recommended/Required Tools
- Creating Your Folder Structure
- Working with the Repo
- How to Use the Multitool
- Class Descriptions
- Ability Distribution Presets
- Progressions
- Localization
- Abilities, Passives, and Spells
- Passive and Spell Lists
- Custom Equipment
- Class Icons
- Skill Icons (TODO)
- Handling Subclasses (TODO)
- Race Entry
- Root Templates
- Character Visuals
- Character Creation
- Localization
- Racial Feats
- Racial Progressions
- Tags
- Custom Icons