Skip to content

The EBF Protocol

Vincent Wong edited this page Mar 6, 2021 · 8 revisions

The Elite Bionics Framework Protocol ("EBF Protocol") is a protocol for letting other C# mods read the true max HP of body parts affected by EBF-enabled Hediffs.

While it is most recommended for C# mods to "connect to" EBF by referencing the EBF DLLs at the birth of the mods themselves, it is still possible to "connect to" EBF later on using C# reflection.

"Clarification required"

C# Modders may have noticed this phrase in the RimWorld Logs when they load their mod with EBF installed:

Elite Bionics Framework has detected some mods using the unmodified GetMaxHealth() method, which violates the EBF protocol. The author(s) of the involved mod(s) should adopt the EBF to clarify their intentions.

The main reason why there is a need of clarification is related to how the RimWorld health system, but in essence, if you make use of BodyPartDef.GetMaxHealth(Pawn), one cannot differentiate between these two cases:

  • General case: you want to know the max HP of a certain body part (e.g. the torso) to calculate some baseline values that is applicable for everyone (e.g. the baseline carrying capacity of a human/Colonist)
  • Specific case: you want to know the max HP of a certain body part of a certain pawn (e.g. the torso of Colonist David) to calculate some values that is only specific to that pawn (e.g. will the next Lancer snipe one-shot the torso?)

Perhaps most modders using BodyPartDef.GetMaxHealth(Pawn) intend to use it in the second, specific way, but we cannot eliminate the first, general way. Moreover, to specify that we intend to use the specific case, a BodyPartRecord instead of a BodyPartDef is required. (Usually, BodyPartRecord leads to BodyPartDef through property access, but never the other way around.)

So no matter how we look at it, some code has to be refactored to confirm compatibility with EBF. Once compatibility is confirmed, the above error message will go away.

Connecting to EBF by referencing the EBF DLL

This is the simpler method to connect to EBF, but requires that mod users also install the EBF at the same time, or a big red ReflectionException: could not find namespace EBF will show up in RimWorld's error log right at the title screen, and the savefile will not be loadable until EBF is also installed. If transitioning from no-EBF to yes-EBF, it is recommended to use the below method using C# reflection.

The steps to do so is not described here since the steps are essentially identical to how a C# development environment is set up to mod the vanilla game, except that you pick the EBF DLL instead of the vanilla game's Assembly-CSharp.dll.

You may obtain the DLL either by Steam Workshop subscription, or by git clone to somewhere outside your repo.

After referencing the DLL in your C# project, refactor your code so that it uses the following methods from EBF:

// first case: general max HP
// this is the official endpoint
float realMaxHealth = EBF.EBFEndpoints.GetMaxHealthUnmodified(BodyPartDef def, Pawn pawn)

// second case: specific max HP
// this is the official endpoint
float realMaxHealth = EBF.EBFEndpoints.GetMaxHealthWithEBF(BodyPartRecord record, Pawn pawn)

Depending on your IDE settings, you may read documentation of those methods in your IDE.

Connecting to EBF by C# reflection

This is the more adaptive method to connect to EBF, does not require mod users to also have EBF, and if done properly, will never raise big red ReflectionExceptions (unless EBF is refactored, but then it will become EBF's problem, not external mods' problems).

While it is recommended to have some knowledge on how C# reflection works, some sample code is provided below to jumpstart development.

// first case: general max HP
// this is the official endpoint
// feel free to optimize this by e.g. making a dedicated function, caching the endpoint during mod load, etc.
float realMaxHealth;
BodyPartDef def;
Pawn pawn;
var makeshiftEndpoint = Type.GetType("EBF.EBFEndpoints")?.GetMethod("GetMaxHealthUnmodified");
if (makeshiftEndpoint != null)
{
    // EBF detected
    realMaxHealth = makeshiftEndpoint.Invoke(null, new Object[] {def, pawn});
}
else
{
    // EBF not detected, use vanilla
    realMaxHealth = def.GetMaxHealth(pawn);
}
// second case: specific max HP
// this is the official endpoint
// feel free to optimize this by e.g. making a dedicated function, caching the endpoint during mod load, etc.
float realMaxHealth;
BodyPartDef def;
Pawn pawn;
BodyPartRecord record;
var makeshiftEndpoint = Type.GetType("EBF.EBFEndpoints")?.GetMethod("GetMaxHealthWithEBF");
if (makeshiftEndpoint != null)
{
    // EBF detected
    realMaxHealth = makeshiftEndpoint.Invoke(null, new Object[] {record, pawn});
}
else
{
    // EBF not detected, use vanilla
    realMaxHealth = def.GetMaxHealth(pawn);
}

A possibly better implementation of the above sample code using Harmony is written by sumghai, available at their MedPods repo as a reference:

https://github.com/sumghai/MedPod/commit/e9757db261256832eb6561efafa7fd6b654ed3a4