Modding:Coding An Item

From bg3.wiki
Revision as of 13:14, 19 December 2023 by CommanderStrawberry (talk | contribs)
Jump to navigation Jump to search

You have made a mesh for a human man, a circlet. We are going to add it to the game. Remember to check your naming conventions for .lsf.lsx files so the multitool correctly converts them, and keep in mind that some lsx files should not be converted to lsf at all. This guide does not cover adding custom icons.

File Overview

Your mod is MySweetMod

  • MySweetMod
    • Generated
    • Public
        • MySweetMod
          • Assets <- models and textures
    • Localization
      • Language <- usually English
        • MySweetMod.loca <- text for items and spells
    • Mods
    • Public
      • Game
        • Assets
      • MySweetMod
        • Assets
        • Content
            • Assets
              • Characters
                • [PAK]_Armor <-- material and mesh LSX for custom outfits and weapons
        • GUI
          • RootTemplates <--- Your Roottemplate lsx goes here. MySweetMod.Lsx
          • Stats
              • Generated <--- TreasureTable.txt - This tells the game where the item will spawn.
                • Data <--- Armor.txt, Object.txt, Passive.txt, Weapon.txt and other TXT files. These contain stats.

Meshes.lsx

First we export our mesh, HUM_M_CLT_Headwear_Circlet_Silver_A, from Blender to the MySweetMod/Generated/Public/MySweetMod/Assets Folder.

We now create 2 lsf.lsx for our MySweetMod\Public\MySweetMod\Content\Assets\Characters\[PAK]_Armor.

In these will be a visualbank (list of meshes),a materialbank (list of material) and a texturebank (list of textures). You can split them into their own pages or combine them all, it doesn't matter. When the multitool converts them they will all get merged together anyway. This seperation is for viewer ease as they can become very long. For our needs we are putting them into 2 lsx, a meshes and a texture/material one.

First, the meshes.lsx.

 <version major="4" minor="0" revision="4" build="602" />
 	<region id="VisualBank">
 		<node id="VisualBank">
           <children>
           (START OF ITEM MESH)
 <node id="Resource">
<attribute id="AttachBone" type="FixedString" value="" />
<attribute id="AttachmentSkeletonResource" type="FixedString" value="" />
<attribute id="BlueprintInstanceResourceID" type="FixedString" value="" />
<attribute id="BoundsMax" type="fvec3" value="0.08509202 1.868147 0.04280814" />
<attribute id="BoundsMin" type="fvec3" value="-0.08422767 1.793357 -0.1403362" />
<attribute id="ClothColliderResourceID" type="FixedString" value="" />
<attribute id="HairPresetResourceId" type="FixedString" value="" />
<attribute id="HairType" type="uint8" value="0" />
<attribute id="ID" type="FixedString" value="00000000000000000000000000001" /> (This UUID links up to the Roottemplate)
<attribute id="MaterialType" type="uint8" value="0" />
<attribute id="Name" type="LSString" value="HUM_M_CLT_MySweetCirclet" /> Referenced nowhere else but try to make it unique so you don't potentially ess up another item's toes.
<attribute id="NeedsSkeletonRemap" type="bool" value="False" />
<attribute id="RemapperSlotId" type="FixedString" value="" />
<attribute id="ScalpMaterialId" type="FixedString" value="" />
<attribute id="SkeletonResource" type="FixedString" value="" />
<attribute id="SkeletonSlot" type="FixedString" value="" />
<attribute id="Slot" type="FixedString" value="Headwear" />
<attribute id="SoftbodyResourceID" type="FixedString" value="" />
<attribute id="SourceFile" type="LSString" value="Generated/Public/MySweetMod/Assets/HUM_M_CLT_Headwear_Circlet_Silver_A.GR2" /> (Remember to set your own filepath for your meshes correctly.)
<attribute id="SupportsVertexColorMask" type="bool" value="False" /> You need this set to True if you want, say, pants to vanish when boots are equipped.
<attribute id="Template" type="FixedString" value="Generated/Public/MySweetMod/Assets/HUM_M_CLT_Headwear_Circlet_Silver_A.Dummy_Root.0" /> (Remember to set your own filepath for your meshes correctly. Dummy Root may need to be renamed in Blender as a bone part depending on what you are making)
<attribute id="_OriginalFileVersion_" type="int64" value="144115207403209020" />

<children>
<node id="AnimationWaterfall">
<attribute id="Object" type="FixedString" value="" />
</node>
<node id="Base">
<children>
<node id="Tags">
<attribute id="Object" type="FixedString" value="Never Hide Hair" />
</node>
</children>
</node>
<node id="ClothProxyMapping" />

<node id="Objects">
<attribute id="LOD" type="uint8" value="0" /> 
<attribute id="MaterialID" type="FixedString" value="00000000000000000000004" /> What material it links up to.
<attribute id="ObjectID" type="FixedString" value="HUM_M_CLT_Headwear_Circlet_Silver_A.HUM_M_CLT_Headwear_Circlet_Silver_A_Mesh.0" /> The .0 denotes mesh load order.
</node>


<node id="Objects"> Extra entry for a Level of Detail mesh, which will be seen from a certain distance from the camera. In this case the LOD1.
<attribute id="LOD" type="uint8" value="1" /> What level of LOD it is
<attribute id="MaterialID" type="FixedString" value="00000000000000000000004" /> 
<attribute id="ObjectID" type="FixedString" value="HUM_M_CLT_Headwear_Circlet_Silver_A.HUM_M_CLT_Headwear_Circlet_Silver_A_Mesh_LOD1.1" /> The .1 denotes mesh load order.
</node>

Note that this is a circlet, so it doesn't need to hide any part of the body. But if it had that code, it would go here. Example:

<node id="VertexColorMaskSlots">
<attribute id="Object" type="FixedString" value="feet" />
</node>

And this code would stop the body's feet from loading in and clipping, ideal for boot items. See here for a full list of Vertex Mask Slots for the body.

</children>
</node>
(END OF ITEM MESH)
 </children>
 </node>
</region>	
</save>                          

Materials and Texture .lsx

Now, we move on to our materialbank.

MySweetMod\Public\MySweetMod\Content\Assets\Characters\[PAK]_Armor

I am not reproducing the entire material here, but should be easy enough to find an item material to copy if you search the SourceFile listed.

<?xml version="1.0" encoding="utf-8"?>
 <save>
 	<version major="4" minor="0" revision="4" build="602" />
 (Beginning of materialbank)
 <region id="MaterialBank">
   <node id="MaterialBank">
   <children>
(BEGINNING OF MATERIAL)


<node id="Resource">
<attribute id="DiffusionProfileUUID" type="FixedString" value="" />
<attribute id="ID" type="FixedString" value="00000000000000000000004" /> ID links up to Meshes visualbank.
<attribute id="MaterialType" type="uint8" value="13" />
<attribute id="Name" type="LSString" value="CircletMaterialName" /> Again this is never referenced anywhere else, but it is best to keep it as unique as you can to prevent clashes.
<attribute id="SourceFile" type="LSString" value="Public/Shared/Assets/Materials/Characters/CHAR_BASE.lsf" />
<children>
<node id="ScalarParameters"> x7
(Cut for length)
There should be 4 Texture2DParameters in your material, for BM, NM, PM, and MSK.
<node id="Texture2DParameters"> NM cape
<attribute id="Enabled" type="bool" value="True" />
<attribute id="ExportAsPreset" type="bool" value="True" />
<attribute id="GroupName" type="FixedString" value="01 Texture Map" />
<attribute id="ID" type="FixedString" value="00000000000000NM" /> This links to texturebank.
<attribute id="ParameterName" type="FixedString" value="normalmap" />
<attribute id="UniformName" type="FixedString" value="Texture2DParameter_normalmap_DefaultWrapSampler" />  These parts tells you what it is.
Look for normalmap, physicalmap, basecolor and MSKColor. We are just going to list the normalmap here.
<node id="Vector3Parameters"> You can use these to set an inherent colour to your material.
<attribute id="BaseValue" type="fvec3" value="0.7667436 0.7667436 0.7667436" />
<attribute id="Enabled" type="bool" value="True" />
<attribute id="ExportAsPreset" type="bool" value="True" />
<attribute id="GroupName" type="FixedString" value="02 Colour" />
<attribute id="IsColor" type="bool" value="True" />
<attribute id="ParameterName" type="FixedString" value="Metal_Primary" /> This one will affect the primary metal of the armour.

<attribute id="Value" type="fvec3" value="1 1 1" /> The 1 1 1 is the colour code here. You can use BG3 Minitool to convert a value.	
</node>

(Cut for length)
</node>
</children>
</node>
(End of material)
</children>
</node>
</region>
(End of MaterialBank)

Now the material we have found, SourceFile "Public/Shared/Assets/Materials/Characters/CHAR_BASE.lsf" is a fairly standard shader without transparency or glowing. Materials that can be Transparent have 'Alpha' in the title. For example, "Public/Shared/Assets/Materials/Characters/CHAR_BASE_AlphaTest_2S.lsf" or "CHAR_BASE_AlphaTest_2S_Dither" will enable alpha and make the mesh visible from both sides (turns off backface culling).

Materials with _VT at the end are useless to us, as they only work with virtual texture. A virtualtexture is essentially a box for textures that makes it load faster. We don't have any way of packing virtualtextures ourselves, so do not attempt to use those materials.


In order to make, say, pants disappear when boots are put on, you may need to use a _Vertcut Material, such as 'Public/Shared/Assets/Materials/Characters/CHAR_BASE_VertCut.lsf', and ensure VertexMask is set to True in the mesh lsx. (As well as vertex painting the item itself the desired colour)

We now add a texturebank to the same lsx.

   <region id="TextureBank">
   <node id="TextureBank">
   <children>
   (texture begins here)
   <node id="Resource">
   <attribute id="Name" type="LSString" value="Generated/Public/MySweetMod/Assets/MSW_CoolCirclet_NM" /> The name is never referenced anywhere else but try to keep it unique.
   <attribute id="ID" type="FixedString" value="00000000000000NM" /> This links up to the node in the materialbank!
   <attribute id="SourceFile" type="LSString" value="Generated/Public/MySweetMod/Assets/MSW_CoolCirclet_NM.DDS" /> Capitalisation is IMPORTANT. Make sure your texture is not named MSW_CoolCirclet_NM.dds or it won't load.
   <attribute id="Format" type="uint32" value="64" /> 
   <attribute id="Width" type="int32" value="1024" /> These dimensions don't need to be exact. I believe if it is smaller than listed, the texture will tile.
   <attribute id="Height" type="int32" value="1024" />				
   <attribute id="Depth" type="int32" value="1" />
   <attribute id="Localized" type="bool" value="False" />
   <attribute id="SRGB" type="bool" value="False" /> This does things. I am not sure what.
   <attribute id="Streaming" type="bool" value="True" />
   <attribute id="Template" type="FixedString" value="" />
   <attribute id="Type" type="int32" value="1" />
   </node>	
   (texture ends here)
   		</children>
           </node>
           </region>	
           </save>

If there is anything wrong with the material, your item may simply refuse to show up, so it is worth double checking everything here.

RootTemplate

Now for the game recognise the circlet as an item, we add it to the root template lsx:

MySweetMod\Public\MySweetMod\RootTemplates\MySweetMod.lsf.lsx

<?xml version="1.0" encoding="utf-8"?>
<save>
   <version major="4" minor="0" revision="6" build="5" />
   <region id="Templates">
       <node id="Templates">
           <children> 

(START OF ITEM)
<node id="GameObjects"> Beginning of Circlet item. You may wish to annotate this with a title
<attribute id="Description" type="TranslatedString" handle="HANDLE2" version="1" /> Generate a handle for this in your .loca file.
<attribute id="DisplayName" type="TranslatedString" handle="HANDLE1" version="1" /> Generate a handle for this in your .loca file.
<attribute id="EquipSound" type="FixedString" value="ea27b98e-5fc0-4b0d-8602-e4a421dd481e" /> Not necessary but you may wish to specify one or it will use whatever default sound is in the parent template.
<attribute id="Icon" type="FixedString" value="Item_Cloth_Headwear_Circlet_Silver_A" /> This is a base game icon- custom icons require addition lsx and texture work.
<attribute id="LevelName" type="FixedString" value="" />
<attribute id="MapKey" type="FixedString" value="0000000000000000000000000002" /> This links up to the armor.txt to form the armor.
<attribute id="Name" type="LSString" value="MY_COOL_NEW_CIRCLET" /> This links up to the armor.txt to form the armor.
<attribute id="ParentTemplateId" type="FixedString" value="4d2e0931-3a01-4759-834b-8ae36749daab" /> This tells the game roughly what kind of item it is, such as in this case a helm. You should use the right kind for your item to avoid inherited problems. For example a shield will not know it is a shield without this ParentTemplate.
<attribute id="PhysicsTemplate" type="FixedString" value="327039da-0827-811f-24e8-fc57e86c7ba2" /> I believe similar to the above, this needs to be roughly the same type as the armour you are adding.
<attribute id="Type" type="FixedString" value="item" />
<attribute id="UnequipSound" type="FixedString" value="f4c24367-83af-484e-bcc5-0438b8e64dec" />  Not necessary but you may wish to specify one or it will use whatever default sound is in the parent template.
<attribute id="VisualTemplate" type="FixedString" value="2136f57a-a35f-c2fe-69fb-29afa9ebc8db" /> This is the mesh mapkey to the LOOT model. The LOOT model is the one that appears when you examine it, or throw the item. For a weapon, the LOOT model and the item model are the same. 
<attribute id="_OriginalFileVersion_" type="int64" value="144115207403209032" />
	<children>
		<node id="Equipment">
			<children>
				<node id="AfroLongHair" />
				<node id="AfroShortHair" />
				<node id="CurlyLongHair" />
				<node id="CurlyShortHair" />
				<node id="DreadLongHair" />
				<node id="DreadShortHair" />
				<node id="LongHair" />
				<node id="ParentRace"> 

The parentrace nodes set the item to use race-specific meshes for githyanki, Dragonborn and Tiefling.  Unless this line is added and  specified, they  will use Human Body or Strong Human Body. (Which may be fine for some races such as Tieflings.)
<children>
<node id="Object">
<attribute id="MapKey" type="guid" value="6503c830-9200-409a-bd26-895738587a4a" /> Tiefling Male
<attribute id="MapValue" type="guid" value="00000000-0000-0000-0000-000000000000" /> 

</node>
(The other parentrace overrides all removed for length as they all follow the same format - the race UUID and the 0 mapvalue)

</children>
				

</node>
<node id="ShortHair" />
		<node id="Slot">
		<attribute id="Object" type="FixedString" value="Headwear" />
		</node>
	       <node id="Slot">
		<attribute id="Object" type="FixedString" value="Hair" />
			</node>
				<node id="VisualSet">
					<attribute id="BodySetVisual" type="FixedString" value="" />
					<attribute id="ShowEquipmentVisuals" type="bool" value="False" />
					<children>
<node id="MaterialOverrides"> Sets colours for this one specific item, overriding the ones set in the material.
<attribute id="MaterialResource" type="FixedString" value="" />
<children>
<node id="MaterialPresets" />

(We can add any colour available to us (cloth, leather, accent) but this circlet is only metal 1 so we only need to list metal 1.)

<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="Metal_Primary" />
<attribute id="Value" type="fvec3" value="0.7667436 0.7667436 0.7667436" /> The colour code in Hex form, you can use BG3 Minitool to convert a value.							
</node>
</children>
</node>
<node id="RealMaterialOverrides" />
</children>
</node>


<node id="Visuals">
<children>

<node id="Object">
<attribute id="MapKey" type="guid" value="71180b76-5752-4a97-b71f-911a69197f58" /> Human Male Race IUUD example. Others Here					
<children>
<node id="MapValue">
<attribute id="Object" type="FixedString" value="00000000000000000000000000001" /> Your Mesh for Human Male
</node>
</children>
</node>
(Removed for length - all the other mapkeys/mapvalues of every race this item is available for)
					</children>
				</node>
				<node id="WavyLongHair" />
				<node id="WavyShortHair" />
			</children>
		</node>
	</children>
</node>						
 </node>
(END OF ITEM)
           </children>
       </node>
   </region>
</save>


Armor and Passives

MySweetMod\Public\MySweetMod\Stats\Generated\Data\Armor.txt

MySweetMod\Public\MySweetMod\Stats\Generated\Data\Passive.txt

Meanwhile in your Armor.txt, they link up like this:

You can look at the games own armor.txt for ideas of what to put here. You can delete what you don't wish to add, all you really need is the entry, the type, what slot it uses and the RootTemplate.

Armor.txt
new entry "MY_COOL_NEW_CIRCLET"
type "Armor"
data "ValueOverride" "4000" (It's a very cool circlet and we want it to cost a lot!)
data "Weight" "0.01"
using "_Head_Magic_Circlet" (This tells it what slot to equip in. Ideally this should match up with the ParentTemplate you set up.)
data "RootTemplate" "0000000000000000000000000002" Matches up to the rootemplate Mapkey.
data "Rarity" "Rare" This effects what colour it is in your inventory, rarer things get a different colour outline. Note that if you set things as common they may spawn in generic containers, which is not optimal for clean uninstalling.
data "Boosts" "UnlockSpell(Target_MAG_HealingWord);UnlockSpell(Shout_MAG_HealingWord_Mass)" This forces it to give us these spells. Note that the character needs spell slots in able to use them! If you want it for everyone you may be better off setting it up as a custom passive.
data "PassivesOnEquip" "MAG_Radiant_Radiating_Helmet_Passive;CUSTOMPASSIVEIMADE" (The ; seperates passives.)
data "Unique" "1" (These may cause you a headache if you put it multiple places, as they won't spawn for people who overlooked the first one. So I don't recommend using these!)
data "MinAmount" "1"
data "MaxAmount" "1" 

If adding a custom passive, your Passive.txt will look like this. You don't need a passive.txt at all, this is just for the ambitious.

The example passive code is borrowed from Isobel's armor.

new entry "CUSTOMPASSIVEIMADE"
type "PassiveData"
data "DisplayName" "HANDLE3;1" (Generate a handle for this in your .loca file.)
data "Description" "HANDLE4;1" Generate a handle for this in your .loca file.)
data "Icon" "CUSTOMPASSIVEICON" (we can set this up in the same way an item icon is set up)
data "DescriptionParams" "DealDamage(1d4, Radiant)"
data "StatsFunctorContext" "OnAttacked"
data "StatsFunctors" "IF(context.HasContextFlag(StatsFunctorContext.OnAttacked) and HasStatus('MAGE_ARMOR') and IsLastConditionRollSuccess(ConditionRollType.ConditionSavingThrow) and not Self() and IsSavingThrow()):DealDamage(SWAP, 1d4, Radiant,Magical)"

Loca

MySweetMod\Localization\English\MySweetMod.loca.xml

You can generate a handle by ticking the Handle box next to 'GENERATE' on the Multitool. It is much like an UUID but it starts with an H. The version number has to do with internal references. Just make all of yours say 1.

<?xml version="1.0" encoding="utf-8"?><contentList>
<content contentuid="HANDLE1" version="1">Circlet</content> 
<content contentuid="HANDLE2" version="1">My circlet is cool.</content>
<content contentuid="HANDLE3" version="1">Circlet Passive</content> 
<content contentuid="HANDLE4" version="1">While the wearer has Mage Armour, each successful Saving throw causes the source of the Saving Throw to take 1-4 Radiant damage.</content>
</contentList>

TreasureTable

MySweetMod\Public\MySweetMod\Stats\Generated\TreasureTable.txt

Now, you need to put it in the treasuretable.

Your treasuretable should look like this:

new treasuretable "TUT_Chest_Potions"
CanMerge 1
new subtable "1,1"
object category "I_MY_COOL_NEW_CIRCLET",1,0,0,0,0,0,0,0 (Do not forget the I_ prefix!)

This will make it spawn in the tutorial chest.

If something goes wrong, it is recommended to overwrite a custom mesh from someone else's mod, or add it to the back of an existing mod. When you know the fault is not with your mesh, you check your materials, and so you can fix things one step at a time.


Bgwiii.png CommunityGuidesModding

Modding guides
Modding resources