CiCisMods-CiCisTrinketAndBindingFramework icon

CiCisTrinketAndBindingFramework

Library for White Knuckle. Lets mods register custom Trinkets, Bindings, and gamemode modes. v2.1.2: Mass Damper now disables leaderboards in endless modes (where it isn't normally available).

Last updated a week ago
Total downloads 6174
Total rating 2 
Categories Tools Libraries
Dependency string CiCisMods-CiCisTrinketAndBindingFramework-2.1.4
Dependants 5 other packages depend on this package

This mod requires the following mods to function

BepInEx-BepInExPack-5.4.2305 icon
BepInEx-BepInExPack

BepInEx pack for Mono Unity games. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

CiCi's Trinket & Binding Framework

A library mod for White Knuckle. Lets other mods register custom Trinkets (run-start picks), Bindings (run-start picks with stronger gameplay effects), and Custom Modes (gamemode-settings toggles like vanilla Hard Mode / Iron Knuckle) so they appear in the picker and the settings panel on Fresh Game.

v2.0.0 breaking change: the 13 mutators that shipped bundled in 1.x have moved to a separate mod, NebulaetrixMutators, maintained directly by Nebulaetrix. Install it to keep Devil Daggers, Marathon, Zen, etc. The framework now exposes the CustomModeRegistry API so any mod can add its own modes the same way Nebulaetrix's does.

This mod does nothing by itself — it's a dependency for other mods.


For players

Just install it. You'll only need it if a mod you use lists this as a dependency — Thunderstore Mod Manager will install it automatically.


For modders

The framework exposes two independent registries:

  • TrinketRegistry — register custom Trinkets and Bindings that appear in the picker on Fresh Game.
  • CustomModeRegistry — register custom gamemode modes (toggles like vanilla Hard Mode / Iron Knuckle) that appear in the settings panel.

The two systems are unrelated — use one, the other, or both.

Setup (shared by both systems)

Reference the framework's DLL in your .csproj, then mark your plugin so BepInEx loads us first:

using BepInEx;
using TrinketAndBindingFramework;
using UnityEngine;
using System.Collections.Generic;

[BepInPlugin("com.yourmod.example", "Example Mod", "1.0.0")]
[BepInDependency(TrinketAndBindingFramework.Plugin.GUID)]
public class ExampleMod : BaseUnityPlugin
{
    private void Awake()
    {
        // Trinket / binding / mode registration goes here.
    }
}

Trinkets & Bindings (TrinketRegistry)

The picker on Fresh Game lists every registered trinket and binding. The framework handles the UI (icon, title, description tint, mutex enforcement, pip cost), unlock bypass, item granting, and leaderboard gating. You register entries once at Awake.

Register a Trinket

A Trinket is a small starter pick — usually a starter item or perk. Examples: Wall Runner (climbing axes), Sawed-Off (shotgun + shells).

TrinketRegistry.RegisterTrinket(
    id:                   "yourmod_my_trinket",        // unique string
    title:                "My Trinket",                 // shown in picker
    description:          "What it does in one line.",
    flavorText:           "Optional italic tagline under the description.",
    cost:                 1,                            // picker slot cost
    icon:                 myIconSprite,                 // 32x32-ish Sprite
    itemsToGrantFactory:  () => new List<Item_Object> { myItemTemplate });

All parameters except id, title, description are optional.

Register a Binding

A Binding is a run-modifier — usually changes gameplay rules and increases score. Examples: Alpine Purist (climbing tools banned, axes red & breakable), Infestation (more enemies).

TrinketRegistry.RegisterBinding(
    id:                    "yourmod_my_binding",
    title:                 "My Binding",
    description:           "Makes the run harder in some way.",
    flavorText:            "Optional flavor.",
    cost:                  1,
    scoreMultiplierBonus:  0.5f,                       // +0.5x run score
    scoreBonus:            0f,                         // flat bonus
    icon:                  myIconSprite);

Same signature as RegisterTrinket — under the hood the framework just flags isBinding=true and tints the picker title differently.

Grant starter items

itemsToGrantFactory runs at run start (not at registration), so you can lazy-build your item templates without worrying about asset-manager readiness at Awake:

itemsToGrantFactory: () =>
{
    try { EnsureMyItemTemplate(); } catch { }
    if (MyItemTemplate == null) return new List<Item_Object>();
    return new List<Item_Object> { MyItemTemplate, MyItemTemplate };  // 2 copies
}

The returned Item_Object instances are template prefabs — vanilla clones them for the player's bag.

Mutex groups (mutually-exclusive picks)

When two of your trinkets shouldn't both be selected (e.g. different starter weapon variants), wire them into a mutex group:

TrinketRegistry.RegisterTrinket(id: "yourmod_sword", title: "Sword", ...);
TrinketRegistry.RegisterTrinket(id: "yourmod_axe",   title: "Axe",   ...);
TrinketRegistry.RegisterTrinket(id: "yourmod_bow",   title: "Bow",   ...);

TrinketRegistry.RegisterMutexGroup(
    groupId: "yourmod_starting_weapon",
    "yourmod_sword", "yourmod_axe", "yourmod_bow");

The picker auto-deselects the others when one is chosen.

Lookup a registered runtime trinket

If you need the live Trinket ScriptableObject for an id you registered (e.g. to check selection state):

var rt = TrinketRegistry.GetRuntimeTrinket("yourmod_my_trinket");
if (rt != null) { /* ... */ }

Custom Modes (CustomModeRegistry)

The gamemode settings panel (the same one Hard Mode and Iron Knuckle live on) accepts custom toggles. Unlike trinkets they have no picker cost, no item grants, and no per-pick UI — each toggle is a checkbox that flips a static bool in your mod so your Harmony patches can branch on it. Examples: Devil Daggers, Marathon Mode, Glass Knuckle (all in NebulaetrixMutators).

The framework handles the toggle UI (difficulty color, hover credit, mutex enforcement, lock visuals), leaderboard gating, save persistence, and run-start flag sync. Mode logic stays entirely in your mod.

Register a Custom Mode

public static class MyMode
{
    public static bool Enabled;
    public static void Set(bool v) => Enabled = v;
}

// In Awake:
CustomModeRegistry.Register(
    id:                   "MyMode",                          // unique string, save key
    displayName:          "My Mode",                          // shown on toggle
    description:          "What the mode does, one line.",
    difficulty:           CustomModeRegistry.Difficulty.Hard, // Easy/Medium/Hard/Extreme, drives toggle color
    onToggle:             MyMode.Set,                         // called when player ticks/unticks and at run-start sync
    exclusiveSettingIds:  null,                               // optional: ids that grey-out when this is on
    disablesLeaderboards: true,                               // optional: ticking this flips the "leaderboards disabled" gate
    hoverCreditText:      "Mode by YourName");                // optional: shown at panel bottom on hover

CustomModeRegistry.InjectIntoGamemode("Campaign");            // required: opt into the gamemode(s) this mode appears on

Important: the mode is invisible until at least one InjectIntoGamemode(...) call lists a gamemode for it. You can call it for multiple gamemodes ("Campaign", "Chimney", etc.).

Write the gameplay patch yourself

The framework only owns the UI and the flag plumbing — your mod is responsible for the actual behavior:

[HarmonyPatch(typeof(ENT_Player), "Movement")]
internal static class MyModePatch
{
    [HarmonyPostfix]
    private static void Postfix(ENT_Player __instance)
    {
        if (!MyMode.Enabled) return;
        __instance.speed = 0.1f;  // do your thing
    }
}

Combo names

When the active mode set exactly matches a registered combo, the green pill list in UI_GamemodeOptionsText swaps to the combo display string instead:

CustomModeRegistry.RegisterCombo(
    new[] { "MyMode", "OtherMode" },
    "<color=#ff9500>Total Annihilation</color>");

Gamemode aliases (for modes that redirect)

If your mode redirects one gamemode to another at load time (Marathon redirects Campaign → GM_Level_Tester), register the alias so the framework's run-start sync still reads the source's saveData:

CustomModeRegistry.RegisterGamemodeAlias("GM_Level_Tester", "Campaign");

Lock a mode as WIP

If a mode isn't shippable yet, mark it locked. The toggle shows the grey vanilla-locked visual, clicks are blocked, and any stale "on" state in saveData is stripped:

CustomModeRegistry.RegisterDisabledLock("MyWipMode", "WIP");

Mutual visual link

When two modes imply each other (one is on, the other "would also activate" if ticked), link them so the other tile tints orange while the first is on:

CustomModeRegistry.RegisterMutualLink("ModeA", "ModeB");

Runtime check

bool on = CustomModeRegistry.IsActive("MyMode");
bool anyOn = CustomModeRegistry.AnyActive();

IsActive reads from the current gamemode's HasActiveSetting, so it tracks the saved selection rather than a local cached bool. Use your own static Enabled flag inside hot paths instead.


Working examples in the wild

  • CiCi's Climbing AxesWall Runner (trinket), Alpine Purist (binding), Free Climber (trinket).
  • CiCi's Pioneer's ShotgunSawed-Off (trinket), Infestation (binding).
  • NebulaetrixMutators — 13 custom modes registered through CustomModeRegistry, plus combo names, a WIP-locked mode, a mutual link, and a gamemode alias. Read its Plugin.cs for an end-to-end example covering every CustomModeRegistry feature.

Compatibility

  • White Knuckle (current build)
  • BepInEx 5.x