Modding:Compatibility

From Caves of Qud Wiki
Jump to navigation Jump to search
This page is about modding. See the modding overview for an abstract on modding.
This page is about modding. See the modding overview for an abstract on modding.

It's usually desirable for mods to be compatible with other mods as well as future changes to the base game. On top of that, it's even possible for future changes to a mod to avoid breaking saves that were already using that mod. This article gives an overview of practices for how to accomplish these things.

Prefixing

Certain internal names must be unique, usually because they're used as a lookup key.

It's easy enough to pick e.g. object blueprint names that don't conflict with things that already exist in the base game, but future updates can introduce new ones, and when other mods are introduced into the equation, all bets are off.

For this reason, you should pick a prefix that's likely to be unique to you and add it to the front of any unique identifiers. E.g.:

<?xml version="1.0" encoding="utf-8"?>
<objects>
  <object Name="ALPHABEARDMODS_CoolNewSword" Load="Merge">
...
  </object> 
</objects>

In this example the unique prefix is ALPHABEARDMODS_.

The following an inexhaustive table of things whose names should be prefixed. Since the names of attributes to use for internal vs. in-game names can be confusing, both are included.

type of data how to set its unique identifier how to set its player-facing name
anatomy Name attribute N/A
body part type [variant] Type attribute Description attribute
event (non-min-event) argument passed to FireEvent N/A
object blueprint ID attribute Render part
option ID attribute DisplayText attribute
part class class name active parts only: NameForStatus field
population table Name attribute N/A
quest ID attribute Name attribute
seeded random generator argument passed to GetSeededRandomGenerator N/A
skill (or "power") Class attribute Name attribute
wish argument passed to WishCommand N/A; the internal name is seen by the player

For classes that are allowed to be in any namespace, using a unique namespace name instead is acceptable.

Merging

When merging onto existing XML data, such as object blueprints, there's no need to copy entire blocks of XML from the base game. Only specify the parts that you want to change and leave out everything else.

For object blueprints, you generally should not include an Inherits attribute at the same time as Load="Merge".

<?xml version="1.0" encoding="utf-8"?>
<objects>
  <object Name="Chain Mail" Load="Merge">
    <part Name="Armor" DV="2" />
  </object> 
</objects>

Random Functions

Main article: Modding:Random Functions

To avoid conflicts and to keep consistency between seeds, Stat.Random() and Stat.Rnd() should not be called. The ideal is to use GetSeededRandomGenerator() for your mod's randomness. The next best is calling RandomCosmetic() or Rnd2().

Stats

Prefer using value over sValue, unless you're creating a unique creature. When loading ObjectBlueprints, the code loads both sValue and value into a stat, then if sValue is set, prefers using that to set a stat over value.

Named Arguments

When interacting with Qud's C# API, prefer using named arguments to fill out optional parameters.

For example, consider XRL.UI.Popup.AskString():

// namespace XRL.UI
// public class Popup
public static string AskString(
  string Message,
  string Default = "",
  string Sound = PROMPT_SOUND,
  string RestrictChars = null,
  string WantsSpecificPrompt = null,
  int MaxLength = 80,
  int MinLength = 0,
  bool ReturnNullForEscape = false,
  bool EscapeNonMarkupFormatting = true,
  bool? AllowColorize = null
)

Without named arguments, calling the function may look like

using XRL.UI;
var response = Popup.AskString(
  "How are you doing?",
  "Okay",
  Popup.PROMPT_SOUND,
  null,
  null,
  80,
  0,
  true,
  true,
  true
);

With named arguments for intentionally-set optional parameters, calling the function may look like

using XRL.UI;
var response = Popup.AskString(
  "How are you doing?",
  Default: "Okay",
  MaxLength: 80, // note: same as current default
  ReturnNullForEscape: true,
  AllowColorize: true
);

This provides a couple of related benefits:

  1. Flexibility and resilience. By specifying only the arguments you want to set, your code will adapt to changes in default values and many common parameter list changes (e.g. the addition of a new optional parameter or the reorganization of optional parameters).
  2. More sensible errors. If the parameter list changes in a way that cannot be automatically resolved (e.g. the removal of a parameter you set), the error message will generally include the name of your argument instead of the index (or worse, continuing to compile with the argument going to the wrong parameter).
  3. Documentation. By naming arguments, you provide clarity to future readers of your code (including yourself) as to its intended purpose.

Avoid including positional arguments after named ones! Calling void Foo(int A, string B, bool C) like Foo(A: 0, "blah", C: false) will cause cryptic errors if the parameter list gets reorganized.

Save Migration

Main article: Modding:Serialization (Saving/Loading)#Migrating between mod versions

When writing scripting mods, you should prefer using IScribedPart, IScribedEffect, and IScribedSystem over IPart, Effect, and IGameSystem, respectively. These will allow you to migrate between saves more easily when you need to add or remove fields from a class.

In cases where you can't directly inherit from one of these classes (e.g. when you need to use IActivePart for its functionality), you can turn a component into a "scribed" version by implementing its Read and Write methods as follows:

public override void Write(GameObject Basis, SerializationWriter Writer)
{   
    Writer.WriteNamedFields(this, GetType());
}

public override void Read(GameObject Basis, SerializationReader Reader)
{   
    Reader.ReadNamedFields(this, GetType());
}

The IScribed* classes have some performance implications you should be aware of that are relevant to certain mods; see the serialization article for more details.

Harmony Patches

Harmony Patches are an advanced technique for code injection/modification. It allows modding parts of the game that are not normally accessible via the normal part and event systems exposed to modders. Harmony patching is recommended only for modders with advanced coding experience. For some advice about when and when not to use Harmony, see Harmony Modding.

Navigation