Modding:General Best Practices

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.
This article may need cleanup to meet quality standards.
Please help improve this page by editing it.

Reason: "This article should be merged into Modding:Overview as well as the main articles for each section."

This article may need cleanup to meet quality standards.
Please help improve this page by editing it.

Reason: "This article should be merged into Modding:Overview as well as the main articles for each section."

Prefixing

Prefix your object and table names with some unique ID.

For instance, alphabeardmods_{id}. This will prevent namespace conflicts with official content and other mods in the future.

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

Merging

Only include your content and the minimum changeset via Load="Merge" in tables and object definitions.

Don't copy the whole contents of the game xml. You only need to include new content.

For objects and population tables, if you're editing an existing item you can use Load="Merge" in it's tag and only specify new items. For example, if I wanted my mod to give chain mail a 2DV:

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

ObjectBlueprint Definitions

In general, unless you're defining a unique object, make sure you add the xml attribute Load="Merge" (and avoid including the Inherits attribute) to prevent breaking behavior.

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* 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.