Skip to content
ProtoMehka edited this page Jul 20, 2026 · 1 revision

Confluence Lib — API Guide

Confluence Lib is a small, generic Forge 1.20.1 library: a named, team-scoped gauge fed by one or more "sources", with a configurable threshold and Forge events fired when that threshold is crossed (in either direction). It has no lore, no entities, no content of its own, and no knowledge of whatever mod is consuming it — it just counts, and tells you when a rule is met.

If you're building a mechanic like "corruption", "reputation", "harmony" (or anything else) that should accumulate per FTB Team from multiple independent triggers, this library handles the bookkeeping so you don't have to touch team NBT or reinvent threshold-crossing logic yourself.

Dependency

Confluence Lib depends hard on FTB Teams — it has no meaning without it, and gauges are always scoped to a Team, never to an individual player directly (a solo player still works fine, since FTB Teams auto-creates a personal team for anyone without one).

There's no public Maven repository for Confluence Lib yet. To depend on it at compile time, flatDir the built jar the same way Cryptic Life Mod does:

repositories {
    flatDir {
        dirs '../confluence-lib/build/libs' // or wherever you keep the jar
    }
}

dependencies {
    implementation "blank:confluencelib:1.0.0"
}

As a player/pack builder, just drop confluencelib-<version>.jar in your mods/ folder alongside FTB Teams — it does nothing on its own until another mod registers a gauge on it.

Core concepts

  • Gauge — a named (ResourceLocation), team-scoped, floating-point value with a threshold.
  • Source — a ResourceLocation identifying why a contribution happened (e.g. mymod:source_something). The gauge only ever sees the ID, never any logic behind it.
  • Threshold rule — a gauge is "at threshold" once its level reaches a configured amount and at least a configured number of distinct sources have contributed at least once. Both conditions must hold; sources are never un-recorded once contributed (only the level itself goes up/down).
  • Confluence Lib never knows why a source contributes or what happens once a threshold is crossed — that's entirely up to the mod that registered the gauge.

1. Register a gauge

Do this once, typically in your mod's FMLCommonSetupEvent:

import fr.protosrv.confluencelib.api.GaugeDefinition;
import net.minecraft.resources.ResourceLocation;

public static final ResourceLocation GAUGE_ID =
        ResourceLocation.fromNamespaceAndPath("mymod", "corruption");

public static void registerGauge() {
    GaugeDefinition.builder(GAUGE_ID)
            .threshold(100f)          // total amount required
            .minDistinctSources(2)    // at least 2 different sources must have contributed
            .decayPerDay(0f)          // optional passive decay, disabled by default
            .register();
}

GaugeDefinition.Builder#register() is a convenience for ConfluenceAPI.get().registerGauge(build()) — use whichever reads better in your code.

2. Contribute to it

Call this from wherever your trigger actually happens (a Forge event listener you already have — Confluence Lib doesn't dictate what those look like):

import fr.protosrv.confluencelib.api.ConfluenceAPI;
import dev.ftb.mods.ftbteams.api.FTBTeamsAPI;
import dev.ftb.mods.ftbteams.api.Team;

public static void onSomethingHappened(ServerPlayer player) {
    FTBTeamsAPI.api().getManager().getTeamForPlayer(player).ifPresent(team ->
            ConfluenceAPI.get().contribute(team, GAUGE_ID,
                    ResourceLocation.fromNamespaceAndPath("mymod", "source_something"), 25f));
}

Calling contribute with a source ID that has already contributed before still adds to the level, it just won't increase the distinct-source count a second time.

3. Reduce it

Symmetric to contribute — for any player action that should lower the gauge (distinct from passive decay below). Never drops the level under 0.

ConfluenceAPI.get().reduce(team, GAUGE_ID, 40f);

4. Read its current state

import fr.protosrv.confluencelib.api.Gauge;

Gauge gauge = ConfluenceAPI.get().getGauge(team, GAUGE_ID);
gauge.getLevel();               // float
gauge.getContributedSources();  // Set<ResourceLocation>
gauge.isThresholdReached();     // level >= threshold AND distinct sources >= minDistinctSources

5. React to threshold crossings

Two Forge events, posted on MinecraftForge.EVENT_BUS, each exactly once per crossing (not repeatedly while above/below):

import fr.protosrv.confluencelib.api.GaugeThresholdReachedEvent;
import fr.protosrv.confluencelib.api.GaugeThresholdClearedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;

@SubscribeEvent
public static void onReached(GaugeThresholdReachedEvent event) {
    if (!event.gaugeId.equals(GAUGE_ID)) return;
    // event.team, event.contributedSources available here
}

@SubscribeEvent
public static void onCleared(GaugeThresholdClearedEvent event) {
    if (!event.gaugeId.equals(GAUGE_ID)) return;
    // event.team available here — gauge dropped back below threshold
    // (via reduce(), decay, or resetGauge())
}

If the gauge crosses back down and then up again later, GaugeThresholdReachedEvent fires a second time — there's an internal "already notified" flag per gauge/team that resets whenever the gauge drops back below threshold, specifically so this can happen.

You don't need these events just to check current state — if you only need "is this team currently past threshold" at some arbitrary moment (e.g. inside a periodic tick check), just call getGauge(...).isThresholdReached() directly instead of tracking your own flag from the events. The events are for reacting to the moment of crossing (a chat message, a sound, spawning something) — see Cryptic Life Mod's ResonanceThresholdHandler for a real example of that distinction (events drive a one-time player-facing signal; a separate ticker reads the gauge directly for continuous gameplay effects).

6. Reset a gauge

Zeroes the level, clears every recorded source, and clears the notified flag (posting GaugeThresholdClearedEvent if it had been past threshold):

ConfluenceAPI.get().resetGauge(team, GAUGE_ID);

7. Optional passive decay

Set decayPerDay(x) on the GaugeDefinition (defaults to 0f, disabled) to have the gauge lose x per real Minecraft day automatically, with no player action needed — useful to avoid infinite accumulation on a long-lived world. Handled by an internal ticker; nothing else to wire up.

Full worked example

Cryptic Life Mod's Resonance gauge is a complete, real reference implementation built on this API — two sources (source_goat_man_aggression, source_toww_encounter), minDistinctSources(1), sleep reducing the gauge, and both threshold events driving a discrete player-facing signal. See:

  • cryptic-life-mod/src/main/java/fr/protosrv/crypticlifemod/resonance/ResonanceSources.java
  • cryptic-life-mod/src/main/java/fr/protosrv/crypticlifemod/resonance/ResonanceThresholdHandler.java
  • cryptic-life-mod/src/main/java/fr/protosrv/crypticlifemod/resonance/ResonanceAggressionTicker.java

Gotchas

  • Register the gauge before anyone contributes to it. contribute/reduce/getGauge all throw IllegalArgumentException for an unregistered gauge ID.
  • Gauges are per-team, never per-player. A player with no FTB Team returns Optional.empty() from FTBTeamsAPI.api().getManager().getTeamForPlayer(...) — decide what that should mean for your mod (Cryptic Life Mod's /resonance command just reports failure).
  • Data is stored under a namespaced key in Team#getExtraData() (confluencelib.<gaugeId>.{level,sources,notified}), so it won't collide with other mods also using getExtraData().
  • minDistinctSources has no effect once satisfied — sources are never removed from the recorded set, only the level moves. Once a team has touched enough distinct sources at least once, only the level threshold matters from then on.

Clone this wiki locally