Developing Mods for PupBase - kleiner-sentry/PupBase GitHub Wiki

Welcome to the PupBase wiki! This mod is intended for programmers with some experience modding for Rainworld.

How to register a new PupType

To start using this mod, you need to create a new PupType. This can be done by using PupManager.Register. You should do this during OnModsInit. You'll need to input the name of your mod, the name of your puptype as a SlugcatStats.Name, and if you wish, your spawnweight.

You must register your own SlugcatStats.Name. However, you don't have to worry about it showing up in the menus (this can be toggled). If you want to set your spawnweight to be anything but 100, you MUST specify that as a parameter while registering your puptype.

If you've done things correctly, your new PupType will show up in the mods Registry.

Here's a basic example:

PupManager.Register(new PupType(Plugin.MOD_NAME, MoreSlugcatsEnums.SlugcatStatsName.Slugpup, 100);

If you want to know how to register a SlugcatStats.Name, I usually make my own class that'll contain all of the names that I'll be using. Then I'll have a register method and a unregister method to go along side it. Here's an example from the stalker:

public class StalkerNames
{
    public static SlugcatStats.Name Stalkerpup;

    public static SlugcatStats.Name StalkerpupAdult;

    public static void RegisterValues()
    {
        Stalkerpup = new SlugcatStats.Name("Stalkerpup", register: true);
        StalkerpupAdult = new SlugcatStats.Name("StalkerpupAdult", register: true);
    }

    public static void UnregisterValues()
    {
        Stalkerpup?.Unregister();
        Stalkerpup = null;
        StalkerpupAdult?.Unregister();
        StalkerpupAdult = null;
    }
}

Modifying the stats

From here, you can modify your puptype in various ways, by augmenting its food pips, allowing it to become an adult, or changing how frequently it'll spawn in where. If your puptype is something that can be referenced, then you can modify its stats quite simply.

PupType pup = PupManager.Register(new PupType(Plugin.MOD_NAME, MoreSlugcatsEnums.SlugcatStatsName.Slugpup, 100);
pup.foodToHibernate = 5;
pup.maxFood = 10;
pup.regionModifiers = [new PupType.RegionModifier("SU") { spawnMultiplier = 1.2f }];
pup.adultModule = new PupType.AdultModule(new SlugcatStats.Name("SlugpupAdult", register: true), 50);

For the more advanced coders among us:

new PupType(Plugin.MOD_NAME, MoreSlugcatsEnums.SlugcatStatsName.Slugpup) { regionModifiers = [new("SU") { spawnMultiplier = 1.2f }], adultModule = new(SlugpupNames.SlugpupAdult) }

^ Do note: this was taken from a list. You don't need to reference the puptype at all in order to modify your stats.

Common use cases

These are probably going to be the most common things that you'll be doing once you've set everything up. Assuming your defined names are publicly available to all of your code, you should be able to use the names that you've defined for yourself. Here's an example taken from the stalker mod:

public static void SlugcatStats_ctor(On.SlugcatStats.orig_ctor orig, SlugcatStats self, SlugcatStats.Name slugcat, bool malnourished)
{
    orig(self, slugcat, malnourished);

    if (slugcat == StalkerNames.Stalkerpup)
    {
        self.bodyWeightFac = malnourished ? 0.45f : 0.65f;
        self.generalVisibilityBonus = -0.3f;
        self.visualStealthInSneakMode = 0.6f;
        self.loudnessFac = 0.5f;
        self.lungsFac = 0.8f;
        self.throwingSkill = 0;
        self.poleClimbSpeedFac = 0.75f;
        self.corridorClimbSpeedFac = 0.75f;
        self.runspeedFac = 0.8f;
    }
    else if (slugcat == StalkerNames.StalkerpupAdult)
    {
        self.bodyWeightFac = malnourished ? 0.75f : 0.85f;
        self.generalVisibilityBonus = -0.3f;
        self.visualStealthInSneakMode = 0.6f;
        self.loudnessFac = 0.5f;
        self.lungsFac = 1f;
        self.throwingSkill = 0;
        self.poleClimbSpeedFac = 0.75f;
        self.corridorClimbSpeedFac = 0.75f;
        self.runspeedFac = 0.8f;
    }
}

This code segment modifies the stats of the pups' personality and behavior. It's very math-intensive.

public static void Player_NPCStats_ctorIL(ILContext il)
{
    ILCursor c = new ILCursor(il);
    if (c.TryGotoNext(
        MoveType.After,
        x => x.MatchStfld<Player.NPCStats>("EyeColor")))
    {
        c.MoveAfterLabels();
        c.Emit(OpCodes.Ldarg_0);
        c.Emit(OpCodes.Ldarg_1);
        c.EmitDelegate(delegate (Player.NPCStats self, Player player)
        {
            if (player.PupType().name == StalkerNames.Stalkerpup || player.PupType().name == StalkerNames.StalkerpupAdult)
            {
                self.Bal = Mathf.Pow(Random.Range(0f, 1f), 0.8f);
                self.Met = Mathf.Pow(Random.Range(0f, 1f), 2f);
                self.Stealth = Mathf.Pow(Random.Range(0f, 1f), 0.3f);
                self.Wideness = Mathf.Pow(Random.Range(0f, 1f), 2f);
                self.S = Mathf.Pow(Random.Range(0.5f, 1f), 0.5f + self.Stealth);
                self.Dark = true;
                self.L = Mathf.Pow(Random.Range(0.8f, 1f), 1.5f + self.Stealth);
                self.EyeColor = Mathf.Pow(Random.Range(0f, 1f), 2f - self.Stealth * 1.25f);

                AbstractCreature.Personality personality = player.abstractCreature.personality;
                personality.nervous = Mathf.Clamp01(Mathf.Pow(personality.nervous + Random.Range(0f, 0.3f), 0.4f + 0.15f * self.Size + 0.15f * self.L + 0.1f * self.Stealth));
                personality.sympathy = Mathf.Clamp01(Mathf.Pow(personality.sympathy + Random.Range(0f, 0.2f), 0.5f + 0.15f * self.Size + 0.15f * self.Met));
                personality.aggression = Mathf.Clamp01(Mathf.Pow(personality.aggression - Random.Range(0f, 0.15f), 1.35f + 0.15f * (1f - self.Size) + 0.1f * (1f - self.Met) + 0.15f * self.Stealth));
                if (float.IsNaN(personality.aggression)) personality.aggression = 0.00000001f;

                // Base Personality Calculations
                personality.dominance = Mathf.Lerp(Random.value, (personality.energy + personality.bravery + personality.aggression) / 3f, Mathf.Pow(Random.value, 0.25f));
            }
        });
    }
}

If you can't use a predefined puptype/name but still want to check if the player/name in question is the puptype you're looking for, here's the type of code I recommend using:

if (PupManager.TryGetPupType(StalkerNames.Stalkerpup, out PupType pupType) && player.PupType() == pupType)

Making this optional

As I have shown with the stalker mod, you can in fact make Pupbase an optional feature. In order to register your puptypes, you must make sure Pupbase is enabled, and then call a separate method that will register all of your puptypes. I recommend you make the "checking if pupbase is enabled" part a boolean for all of your code to use, to hasten things. Here's a few segments of code taken from the stalker mod:

public static bool PupBase = false;

StalkerNames.RegisterValues(); // registers Stalkerpup and StalkerpupAdult as a SlugcatStats.Name
if (ModManager.ActiveMods.Any(mod => mod.id == "Antoneeee.PupBase"))
{
    PupBaseInit();
}

public static void PupBaseInit()
{
    _ = PupManager.Register(new PupType(MOD_NAME, StalkerNames.Stalkerpup, spawnWeight: 50) { regionModifiers = [new("LF") { spawnMultiplier = 1.2f } ], campaignModifiers = [new("Stalker") { spawnMultiplier = 1.2f }, new("Reaper") { spawnMultiplier = 0.9f } ], adultModule = new(StalkerNames.StalkerpupAdult) });
    PupBase = true;
}

^ Do note: The reason why I've defined this puptype as "_" (AKA undefined) is because I don't actually want to define it. To define it would force your mod to require Pupbase to be installed (otherwise you'll get errors and your mod might not work at all).

As a general rule of thumb: any bit of code that uses my mod should be made as its own method. Anything that calls those methods must also check if my mod is enabled. This check cannot be inside the methods. It can be tedious, but it's necessary if you don't want your mod to break. Here's an example that I use in the stalker mod:

if (Plugin.PupBase && Plugin.CheckPupCompatibility(player)) // A snippet of code demonstrating that you MUST check if Pupbase is enabled first before running any bit of code that uses Pupbase.

public static bool CheckPupCompatibility(Player player)
{
    if (PupManager.TryGetPupType(StalkerNames.Stalkerpup, out PupType pupType) && player.PupType() == pupType)
    {
        return true;
    }
    return false;
}

I don't have any more examples to show, so hopefully you get the gist of what needs to be done.

Misc

Other bits of info that you should know.

  • The point in which the pup has its PupType generated is in Player.NPCStats, before the stats are created.
  • PupManager blacklists certain IDs, which is something modders can modify.
  • If you want to see what PupBase is doing, you can go to Rain World\BepInEx\LogOutput.log. If you want to see even more info, enable the debug setting in the remix menu.