ShrinkingZone
A closing perimeter that shrinks over time, damaging players caught outside it based on their max health. Sound effects are available but OFF by default following reports of motion sickness.
About this mod Author’s original text
ShrinkingZone — ROUNDS Mod
A closing "safe zone" for ROUNDS: a circular perimeter that shrinks over time,
damaging any player caught outside it (battle-royale ring style).
Files
- ShrinkingZonePlugin.cs — BepInEx plugin entry point, hooks round start/end.
- ZoneController.cs — spawns the ring visual, shrinks it, and damages players outside it.
- ShrinkingZoneMod.csproj — build project; references the 4 game DLLs needed to compile.
- manifest.json — package manifest.
Requirements
- BepInEx (5.4.x) installed for ROUNDS
- UnboundLib — round lifecycle hooks
- (Optional) ModdingUtils — extra player/health helpers if you want cleaner damage calls
- Visual Studio / Rider / VS Code with the.NET SDK
- References to Assembly-CSharp.dll, UnityEngine.dll, BepInEx.dll, and UnboundLib.dll
from your ROUNDS install (ROUNDS/BepInEx/plugins/ and ROUNDS/ROUNDS_Data/Managed)
Verified against Assembly-CSharp.dll / UnboundLib.dll via dnSpy
Every field/method/attribute name below has been confirmed against the real
game and library assemblies — no more guessing left in this mod. Kept here as
a reference in case you update BepInEx/UnboundLib/the game later and need to
re-check anything that might have changed.
- ~~How to enumerate active players~~ ✅ Confirmed: PlayerManager.instance.players
is a public List players. Code already matches this exactly.
- ~~How to apply damage, and how to read max health~~ ✅ Confirmed:
- Player.data is a CharacterData with plain public floats
health and maxHealth.
- Damage is dealt through a DamagableEvent (subclass of abstract
Damagable) sitting on the player's GameObject — accessed via
player.GetComponent() since no cached field was found.
- Real signature: `TakeDamage(Vector2 damage, Vector2 damagePosition,
GameObject damagingWeapon = null, Player damagingPlayer = null,
bool lethal = true, bool ignoreBlock = false). Note damage` is a
Vector2 (magnitude = amount, direction = knockback), not a float —
ZoneController.ApplyZoneDamage now builds this vector pointing back
toward the zone center.
- ~~Round start/end hook names~~ ✅ Confirmed: GameModeHooks is a
public static class holding plain const string identifiers —
HookRoundStart = "RoundStart" and HookRoundEnd = "RoundEnd" — exactly
matching what ShrinkingZonePlugin.cs already uses.
All three items above are confirmed — see the "Multiplayer / networking"
section below for the remaining (now also resolved) networking-specific checks.
Tuning
All the knobs are public static fields at the top of ShrinkingZonePlugin:
StartRadius = 30f;
FinalRadius = 3f;
ShrinkStartDelay = 5f;
ShrinkDuration = 25f;
DamagePercentPerSecond = 0.05f; // 5% of the player's MAX health per second
DamageTickInterval = 0.5f;
Damage scales off each player's max health rather than a flat number, so
the zone hurts proportionally regardless of health-boosting cards a player has
picked up. At the defaults, a player fully outside the zone loses 5% of their
max health every second (i.e. dies in ~20s of standing outside it).
Multiplayer / networking
The zone is now host-authoritative:
- Only the master client (PhotonNetwork.IsMasterClient) advances the
shrink timer and computes the "true" radius.
- Every SyncInterval (default 0.2s) the host broadcasts `(center, radius,
timer) to all clients via NetworkingManager.RPC(...)`, landing on the
static [UnboundRPC] RPCA_SyncZoneState method on every machine.
- Non-host clients never compute their own radius — they just render whatever
the last synced value was. This is what keeps everyone's ring visually
identical instead of drifting apart from frame-rate/timing differences.
- Damage is still applied locally per-client, but only for players that
client actually owns (PhotonView.IsMine), to avoid multiple machines
applying the same damage tick to the same player.
Things to verify for the networking layer specifically
- ~~NetworkingManager.RPC signature~~ ✅ Confirmed:
public static void RPC(Type targetType, string m
Install & use Install: BepInEx plugin
- The archive from playbuff is the mod itself — usually a BepInEx plugin (a .dll or a plugin folder once unzipped).
- Recommended: use a community mod manager (such as r2modman) — pick this game, import the mod, and the manager sets up BepInEx and its dependencies for you.
- Manual route: install the BepInEx loader in the game folder, then drop the unzipped .dll or plugin folder into BepInEx/plugins/.
- Launch the game — the BepInEx console logs every plugin it loads; seeing this mod in the log means it is installed.
Mods with listed dependencies need those installed too, or they will not load. Options are usually changed in-game or in the plugin's config file.
Install these first, or the mod will not work: