[Wrath] Blueprints - WittleWolfie/OwlcatModdingWiki GitHub Wiki

Last Updated For Game Version: 1.0.4

Almost anything you want to change in the game will involve blueprints. Blueprints are roughly what you'd expect: plans that describe how an in-game entity can be created and should behave. Entity is a loose term here: it could be a class, feat, spell, ability, item, dialog tree, area, etc.

SimpleBlueprint

  • Code: Kingmaker.Blueprints

This the root class for all blueprints in the game. For the most part this class isn't very interesting; it serves primarily to host four common elements:

  • name
  • AssetGuid
  • m_AllElements
  • m_ValidationStatus

name and m_ValidationStatus are largely irrelevant; name is used in relevant log events and m_ValidationStatus appears to be a feature used to validate the configuration of blueprints inside of the internal tool used for game development.

AssetGuid is discussed in this section, but look to ElementsArray in the next section for discussion of m_AllElements. The rest of the code in this class is there to handle serialization.

AssetGuid

AssetGuid is a unique identifier used to fetch the blueprint from the game library. This is nearly always used in one of two ways:

// Fetch shaken buff blueprint by GUID
SimpleBlueprint blueprint = ResourcesLibrary.TryGetBlueprint("25ec6cb6ab1845c48a95f9c20b034220");
BlueprintBuff shaken = blueprint as BlueprintBuff;

// Convert to a reference, see Kingmaker.Blueprints.BlueprintReferenceEx
BlueprintBuffReference shakenRef = shaken.ToReference<BlueprintBuffReference>();

The second use, converting to a reference, requires fetching the blueprint first. By writing a utility function and using Kingmaker.Blueprints.BlueprintReferenceEx, the code above can be simplified:

BlueprintBuffReference shakenRef =
    BlueprintTool.Get<BlueprintBuff>("25ec6cb6ab1845c48a95f9c20b034220").ToReference<BlueprintBuffReference>();

public static class BlueprintTool
{
  public static T Get<T>(string guid) where T : SimpleBlueprint
  {
    var assetGuid = new BlueprintGuid(System.Guid.Parse(guid));
    SimpleBlueprint asset = ResourcesLibrary.TryGetBlueprint(assetGuid);
    T result = asset as T;
    if (result == null)
    {
      // Log an error here - the blueprint wasn't found or is of the wrong type.
    }
    return result;
  }
}

Blueprint references are used frequently to group blueprints such as spells in a spell list or bonus feats for a specific class.

BlueprintScriptableObject

  • Code: Kingmaker.Blueprints

While there are other child classes of SimpleBlueprint, BlueprintScriptableObject is the most widely used. This class contains a property called ComponentsArray which contains most of the rules that define a blueprint's behavior.

ComponentsArray / BlueprintComponent

  • Code: Kingmaker.Blueprints.BlueprintComponent

Specific blueprint sub-classes have some custom behavior and fields, but most basic game mechanics and interactions are defined through the use of blueprint components. As a simple example, here is the code for the AddSpellSchool component:

[AllowedOn(typeof(BlueprintBuff), false)]
[TypeId("373361accb91426f9fd87ac84ae34ba0")]
public class AddSpellSchool : BlueprintComponent
{
  public override void ApplyValidation(ValidationContext context)
  {
    base.ApplyValidation(context);
    if (this.School == SpellSchool.None)
    {
      context.AddError("School is not specified", Array.Empty<object>());
    }
  }

  public SpellSchool School;
}

Based on the name the use of this component is clear: it defines the spell school associated with that blueprint. Notice that the only thing inside of this component is ApplyValidation() and the School property. Neither is generic, and BlueprintScriptableObject only has a container with references to BlueprintComponent. How then, is this component actually used?

The answer lies in Kingmaker.Blueprints.BlueprintExtenstions:

public static void CallComponents<T>(this BlueprintScriptableObject blueprint, Action<T> action) { }

public static T GetComponent<T>(this BlueprintScriptableObject blueprint) { }

public static IEnumerable<T> GetComponents<T>(this BlueprintScriptableObject blueprint) { }

BlueprintComponent doesn't define common functions implemented in child classes; instead it is as a container for a wide variety of component behavior that can be easily queried by the game to implement a blueprint. In the case of AddSchoolComponent, it is called in Kingmaker.UnitLogic.Mechanics.MechanicsContext:

if (this.SpellSchool == SpellSchool.None)
{
  AddSpellSchool component3 = this.AssociatedBlueprint.GetComponent<AddSpellSchool>();
  this.SpellSchool = ((component3 != null) ? component3.School : SpellSchool.None);
}

MechanicsContext deserves its own discussion, but in short it is a container for contextual information related to a game event such as a character casting a spell.

Because components are used this way, it's not always obvious how a component achieves an effect. To understand it you need to look at specific fields and functions provided by a component and see where they are referenced. Not all components will be completely unique in the way they're used; intermediate component classes or interfaces define common behavior and usage.

For example, Kingmaker.UnitLogic.Abilities.Components.Base.IAbilityTargetRestriction:

public interface IAbilityTargetRestriction
{
  bool IsAbilityRestrictionPassed(AbilityData ability);

  string GetAbilityRestrictionUIText();
}

Several components implement this interface which determines whether a given target is valid for an ability. If you were to look for usages of IsAbilityRestrictionPassed() in an implementation of this interface, nothing would show up in dnSpy. Instead you need to look for usages of the interface method.

Usage Restrictions

Notice the first attribute on AddSpellSchool:

[AllowedOn(typeof(BlueprintBuff)), false)]

This has no effect on the code but it indicates which blueprints can make use of a given component type. If you add a component to an unlisted blueprint type it may work, but often it will never even be referenced. Unless you really need to, it's advisable to only use components inside of explicitly supported blueprint types.

If a component has no AllowedOn attribute it should work on any blueprint. Make sure you check the component's parent classes for attributes as well.

There is one other attribute that restricts component usage:

[AllowMultipleComponents]

By default a blueprint only supports a single instance of a component type. Components annotated with AllowMultipleComponents can be added as many times as needed.

Example: Using Components

Take a look at the components for the Spell Penetration feat:

Component Field Value
SpellPenetrationBonus Value ContextValue of 2
Descriptor UntypedStackable
RecommendationRequiresSpellbook
PrerequisiteStatValue Stat Intelligence
Value 3
FeatureTagsComponent FeatureTags Magic
  1. SpellPenetrationBonus
    • When a character has the SpellPenetration feature this fact is applied. In this case, there is a special fact class which implemented the logic for bonuses to spell penetration.
    • The value of the bonus is +2 as defined by the ContextValue.
    • The UntypedStackable descriptor determines stacking behavior for the bonus; it will stack with any other spell penetration bonuses that character has.
  2. RecommendationRequiresSpellbook
    • Since most feats are available for every character regardless of how useful they are, several recommendation components exist that determine whether a feat should be recommended for a character.
    • When a character without a spellbook is leveling up this feat will be at the end of the list with a thumbs down.
  3. PrerequisiteStatValue
    • A character cannot take this feat unless they have at least 3 Intelligence.
    • This is odd to see; pathfinder does not have any requirements for the Spell Penetration feat. This is likely to make it an illegal feat for animal companions.
  4. FeatureTagsComponent
    • Feature tags are used in tooltips and in search bars.
    • If you search for "Magic" when selecting a feat during level up, Spell Penetration will show up.

Type ID

For the most part, the TypeId annotation on a component class is not relevant:

[AllowedOn(typeof(BlueprintBuff), false)]
[TypeId("373361accb91426f9fd87ac84ae34ba0")]
public class AddSpellSchool : BlueprintComponent

However, if you are modding exclusively using the JSON based blueprint format in Owlcat's WrathModificationTemplate this type is necessary to represent an object.

Here is Spell Penetration's components in that format:

"Components": [
  {
    "$type": "7a506f785235d39409ef96dd26903afb, SpellPenetrationBonus",
    "Value": {
      "$type": "<Unknown GUID>,  ContextValue",
      "Value": 2
    },
    "Descriptor": "UntypedStackable"
  },
  {
    "$type": "3eff78016821c464391135dd266415f8, RecommendationRequiresSpellbook"
  },
  {
    "$type": "04406431439974e489cc8fdea779cf46, PrerequisiteStatValue",
    "Stat": "Intelligence",
    "Value": 3
  },
  {
    "$type": "6d43a0c88e994d04bd2859978dec9337, FeatureTagsComponent",
    "FeatureTags": "Magic"
  }
]

Currently this would not work because the type GUID for ContextValue is unknown. While every BlueprintComponent has an explicit TypeId annotation, most other classes do not. As a result, those classes cannot currently be represented in JSON.

You should be able to query the type GUID of any unknown class at runtime, see Type.GUID. Unfortunately this does not seem to work consistently and any GUIDs discovered this way are not guaranteed to be stable. An update to the game or a build for a different platform may require a different GUID.

Because of this it is not recommended to rely exclusively on the JSON blueprint and patch formats for modding.

ElementsArray

ElementsArray is typically populated during de-serialization. This means that any components added programmatically will not populate it. As a result, any new blueprints you add that rely on its contents should explicitly populate them.

Currently its usage is fairly limited and it's not entirely clear when it should be populated. References suggest it may be important when using the Selective Spell metamagic and for AoE abilities.

If you have more examples and experience, please update this wiki section! In particular it would help to share an understanding of when it is necessary to populate ElementsArray or if there is a code path that automatically populates it.

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