ROUNDS
You are viewing a potentially older version of this package. View Latest Version
Install

Details

Date Uploaded
3 days ago
Downloads
181
Size
25KB
Dependency string
Skler-ShrinkingZone-1.0.0

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 — Thunderstore 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.

  1. How to enumerate active playersConfirmed: PlayerManager.instance.players is a public List<Player> players. Code already matches this exactly.
  2. How to apply damage, and how to read max healthConfirmed:
    • 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<DamagableEvent>() 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.
  3. Round start/end hook namesConfirmed: 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

  1. NetworkingManager.RPC signatureConfirmed:
    public static void RPC(Type targetType, string methodName,
        RaiseEventOptions options, SendOptions sendOptions, params object[] data)
    
    Code updated to pass RaiseEventOptions.Default and SendOptions.SendReliable before the trailing float args. Added using ExitGames.Client.Photon; and using Photon.Realtime; for these types (standard PUN2 namespaces) — if the build can't find them, recheck their actual namespace in dnSpy.
  2. [UnboundRPC]Confirmed: public class UnboundRPC : Attribute in namespace UnboundLib.Networking — already covered by the existing using UnboundLib.Networking; at the top of ZoneController.cs. No changes needed.
  3. PhotonView on Player ✅ Using player.GetComponent<PhotonView>() — no cached field was found, so this resolves it the same way as DamagableEvent.
  4. Does HookRoundStart fire on all clients or just host? — the one item we haven't explicitly confirmed. If it turns out to be host-only in your UnboundLib version, the SpawnZone call in ShrinkingZonePlugin.OnRoundStart needs to be wrapped in its own RPC so every client creates a local ZoneController to receive syncs. Easiest way to check: just build and test in a 2-player session — if the non-host player never sees the ring at all, this is why.

Everything else in this mod has now been verified against the real assemblies via dnSpy — the pattern (host computes truth, broadcasts on an interval, clients render/apply damage locally) is standard for ROUNDS mods, and the exact method/field names are locked in.

Building

  1. Open ShrinkingZoneMod.csproj and edit the <GamePath> property near the top to point at your actual ROUNDS install folder, e.g.:

    <GamePath>C:\Program Files (x86)\Steam\steamapps\common\ROUNDS</GamePath>
    

    Everything else (Managed folder, plugins folder) is derived from this.

  2. Double-check the reference filenames actually match what's on disk — open your ROUNDS_Data\Managed and BepInEx\plugins folders and confirm:

    • Photon3Unity3D.dll, PhotonUnityNetworking.dll, PhotonRealtime.dll, and UnityEngine.CoreModule.dll exist with those exact names (Unity/Photon DLL naming varies a bit between versions — if a name doesn't match, update the <HintPath> in the .csproj accordingly).
    • The UnboundLib subfolder name under BepInEx\plugins\ matches what's in the .csproj (Willis81808-UnboundLib is a guess based on common naming — yours may differ, especially if you installed via a mod manager that names folders differently).
  3. Build:

    dotnet build
    
  4. Copy the resulting ShrinkingZoneMod.dll (in bin/Debug/netstandard2.1/) into ROUNDS/BepInEx/plugins/.

  5. Launch the game and check the BepInEx console window (should pop up alongside the game if console logging is enabled in your BepInEx config) — any reference/load errors will show up there immediately.

Packaging for Thunderstore

Zip manifest.json, an icon (icon.png, 256x256), a README.md, and the built .dll together, then upload via the Thunderstore package manager.

Thunderstore development is made possible with ads. Please consider making an exception to your adblock.