Game activity + rules
GameActivity is a scoped bundle of event listeners and GameRule toggles tied to one game instance’s dimension. Every listener you register lives on a child EventNode (on the core ConduitEvents bus) that is detached on close(), so they auto-unregister when the match ends, no manual teardown, no leaks, no double-fires. Rule toggles (no fall damage, no PvP, keep hunger topped up…) are enforced by the engine’s GameRuleEnforcer via Fabric callbacks, so a minigame gets the common behaviours with zero per-game boilerplate.
This is Nucleoid Plasmid’s GameActivity / GameRuleType idea, ported onto Conduit’s event bus.
When to use it
Section titled “When to use it”- You want listeners that automatically disappear when the round ends.
- You want the common minigame rule toggles (no fall damage, no hunger, no block break) without writing the Fabric callback plumbing yourself.
- You want all of a match’s behaviour scoped to its instance dimension, so concurrent lobbies don’t cross-fire.
package me.zlex.conduit.game;
public final class GameActivity implements AutoCloseable { public GameActivity(ResourceKey<Level> dimension);
public ResourceKey<Level> dimension(); public EventNode node(); // child of the global root
public <E extends ConduitEvent> GameActivity listen(Class<E> type, Consumer<E> handler);
public GameActivity deny(GameRule rule); // turn a rule off public GameActivity allow(GameRule rule); // turn it back on public boolean denies(GameRule rule);
@Override public void close(); // detach listeners + rules (idempotent)}
public enum GameRule { ALL_DAMAGE, // cancel all damage to players in the dimension FALL_DAMAGE, // cancel fall damage PVP, // cancel player-vs-player damage HUNGER, // keep hunger topped up (no starving) BLOCK_BREAK // disallow breaking blocks}Example
Section titled “Example”import me.zlex.conduit.event.player.PlayerDamageEvent;import me.zlex.conduit.game.GameActivity;import me.zlex.conduit.game.GameRule;import me.zlex.conduit.instance.GameInstance;
public final class MyRound {
private GameActivity activity;
void start(GameInstance inst) { activity = new GameActivity(inst.dimension()); activity.deny(GameRule.FALL_DAMAGE) .deny(GameRule.HUNGER) .deny(GameRule.BLOCK_BREAK);
activity.listen(PlayerDamageEvent.class, e -> { if (inLobby(e.player())) e.setCancelled(true); }); }
void end() { if (activity != null) { activity.close(); // detaches every listener + rule for this dimension activity = null; } }}Because GameActivity is AutoCloseable, you can also scope it with try-with-resources when the whole match runs inside one block:
try (GameActivity activity = new GameActivity(inst.dimension())) { activity.deny(GameRule.PVP); // ... run the match; on exit everything is torn down.}deny/allow/listenreturnthis, so they chain.close()is idempotent: calling it twice is harmless. It removes the activity’s child node from the global root and unregisters its rules.- Listeners registered here only fire for events on this activity’s node; the engine routes per-dimension events (like
PlayerDamageEvent) so two concurrent instances don’t see each other’s events. - Rules are enforced engine-side; you only declare them. To change a rule mid-match, call
deny/allowagain on the live activity.