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

Details

Date Uploaded
3 days ago
Downloads
4K
Size
2.4MB

Very Balanced Rounds Mod

Custom cards for ROUNDS, built on UnboundLib, with ModdingUtils for deck surgery and RarityLib for the Legendary tier.

Cards

Card Rarity Effect
Glasser Cannon Rare +200% damage, -75% health
McDonalds Common +400% health, -50% movement speed
Gremlin Common -30% size, +50% movement speed
Whipped Common +100% movement speed, -20% health
Density Common +70% health, -40% size
Leg Day Common +100% jump height, -0.5s block cooldown
Fraud Uncommon -30% size, -50% block cooldown
Super Quick Reload Uncommon -90% reload time, -20% damage
Lowe's Worker Uncommon -25% damage, heavy bullet drop, +150% damage to enemies below you
Invisible Bullets Uncommon Your bullets do not render
MTG Player Uncommon Touching you deals 50% of your bullet damage
Giga Drum Mag Rare Magazine maxed out, reload 20x longer
Sword Rare +777% damage, +75% movement speed, very short bullet range
Card Counter Rare +15% damage per card held, +0.5s reload
Titan Killer Rare Bullets deal +5% of the target max health
Random Crits Rare Every bullet has a 2% chance to instantly kill
Bird Killer Uncommon Your kills cannot be undone by Phoenix
Inevitable Rare +5% damage per second, compounding, resets each point
Rev Up Rare -50% attack speed, +5% per second, resets each point
Icon of Sin Legendary +5%/s damage, health, bullet and move speed; -5%/s reload and cooldown
Particle Accelerator Rare +50% movement speed per hit dealt, compounding, resets each point
Infinity Rare Enemy bullets freeze in place when they get close
Black Hole Rare Nearby players are pulled toward you
Tree Legendary +5000% health, immune to knockback, cannot move at all (unless you have The Lorax)
The Last Breath Rare At 20% health: full heal, invincible and doubled, then dead in 5s
Chris's Side Project Legendary No block cooldown; dead at 7 seconds if the point is not over
The Kirk Special Rare Every bullet hit explodes the victim 1s later for 2.5x your bullet damage; stacks per pellet
Portal Gun Rare Blocking teleports you to your cursor; +0.5s block cooldown
Prestige Rare Destroys your other cards, +100% health, damage and attack speed
Touch the Wall Legendary Your next 3 picks only offer Legendary cards
Conner's Pride Rare +100% health, damage and attack speed — in June only
Eight Handled Sword Divine General Mahoraga Legendary Each hit you take cuts further damage by 10%, compounding
Ihateblockbuild Uncommon Blocked bullets still deal 25% of their damage
Iloveblockbuild Rare Every block heals 25% of your max health
Balanced Uncommon Health and bullet damage both become their average
AK Uncommon +50% bullet speed, +10 ammo, no bullet gravity, +1s reload
Up Uncommon Bullet gravity reversed
Down Common Your gravity and bullet gravity x2
Go Blue Uncommon If you die and everyone else dies within a moment, you win
Claude's Choice Rare A surprise. Deliberately undocumented - read src/Cards/ClaudesChoiceCard.cs only if you want it spoiled
Conner Hack Special Rare 3.5x damage at point blank on a steep curve; ~1.4x at 20 units, 0.5x across the map
Onceler Uncommon Hitting a Tree holder kills them instantly (unless they have The Lorax)
Banana Uncommon Stand still 5s, then heal 10% max health per second
Xane's Wire Management Rare Every card you hold is swapped for a random one of the same rarity
Border Hop Uncommon -80% damage from the map edge, -10% movement speed
Ronald McDonald Rare Bullets deal +25% of your current health, -20% attack speed
Hitscan Rare +1000% bullet speed, -40% attack speed
Railgun Rare +50% of bullet speed added as damage, +20% bullet speed, +1.5s reload
Discord Mod Rare Nearby players pushed away, harder the closer they are
Hollow Purple Legendary Massive purple bullets, +200% damage, ignore walls and gravity, -80% bullet speed, +60% cooldown, +4s reload
Dense Bullets Uncommon Much smaller bullets, +60% damage, +0.5s reload
The Block Build Special Legendary Shooting triggers your block effects (not the shield), -90% health
Killdozer Legendary Contact damage scaling with your speed and bullet damage, +25% movement speed
Shulker Uncommon Players you hit are launched upward as if they jumped with 1000% jump height
Drone Strike Rare Unlimited jumps in midair; picking it also gives you Homing and Explosive Bullet
C-Ram Rare Attack speed squared (150% becomes 225%), +2s reload; each copy squares again
The Island Uncommon Lose 2 random cards when picked, -75% block cooldown
The Lorax Rare Frees a Tree holder to move, immunity to Onceler, -40% health
Super Shotgun Rare +12 bullets, +9 ammo, -40% attack speed, range a little longer than Buckshot
500 Cigarettes Rare +150% damage; toxic clouds heal you instead; lose 5% max health per second
Atomic Bomb Legendary Bullets per shot equal your max ammo, +50% damage, +10s reload
ASU Frat Leader Rare Bullets deal +(100 x size ratio)% damage to smaller players, and damage x size ratio to bigger ones
The Flavorful Garlic Uncommon yumby. Deliberately undocumented - read src/Cards/FlavorfulGarlicCard.cs only if you want it spoiled
Projectile Dysfunction Uncommon Bullets wander in random, ever-changing directions, +100% damage
Rare Artifact Rare -20% damage taken from everything
Legendary Artifact Legendary -40% damage taken from everything
Daedalus Stormbow Legendary Bullets fall from the top of the map toward your cursor, a little to one side at random, and go through walls
Patience Legendary Other players can't shoot for the first 3 seconds of each point

Building

Requires the .NET SDK (8 or newer). From the project root:

dotnet build

That compiles the mod and copies VeryBalancedRoundsMod.dll straight into the Thunderstore profile it was set up against, so the next launch picks it up.

Useful overrides:

dotnet build -p:DeployToRounds=false                 # compile only, don't copy
dotnet build -p:ProfileDir="...\profiles\Default"    # deploy somewhere else
dotnet build -p:RoundsDir="D:\...\common\ROUNDS"     # non-default game install

To produce a Thunderstore-ready zip in dist/:

dotnet build -t:Package

Adding a card

  1. Copy src/Cards/GremlinCard.cs to a new file and rename the class.
  2. Add its title to src/Util/CardNames.cs.
  3. Register it in VeryBalancedPlugin.RegisterCards().
  4. dotnet build, then relaunch ROUNDS.

Things worth knowing before you tune numbers:

  • SetupCard runs once at game start, not on pickup. The values you assign to gun and statModifiers there are multipliers over the player's current stats: 2f doubles, 0.5f halves, 1f is neutral. Several fields are additive instead — Gun.ammo, Gun.reloadTimeAdd, Gun.percentageDamage, Block.cdAdd, CharacterStatModifiers.regen — and those treat 0f as neutral. Gun.destroyBulletAfter is neither: it's a plain override that the game ignores when zero, so the last card to set it wins.
  • Gun.attackSpeed is a cooldown, not a rate. Bigger is slower. 2f means half speed.
  • Magazine size is hard-capped at 90 by Mathf.Clamp(maxAmmo, 1, 90) in the game's own stat code. No card can exceed it.
  • Total gun damage has a floor of 0.25x, also clamped by the game.
  • Bullet size is Gun.size, additive padding on the bullet's collision trail (so it changes the hitbox, not just the sprite): the radius is damage^0.85 / 400 + 0.3 + size. Gun.projectileSize also exists and nothing in the game reads it. The game has no floor; this mod puts one at 0.05 (RayCastTrail_Start_Patch) so stacked Dense Bullets still hit.
  • Block cooldown is (cooldown + cdAdd) * cdMultiplier with no floor, so a zero multiplier really is instant.
  • Override the five-argument SetupCard when you need the player's Block. That's the overload CustomCard.Awake actually calls; its default forwards to the four-argument one.
  • GetStats() only draws the text on the card face. It doesn't affect gameplay, so it will happily lie to your players if you change SetupCard and forget to update it.
  • Card text convention. Stat rows are only for changes to a stat (damage, health, speeds, reload, ammo, gravity, size, block cooldown), including scaling ones like "+5% per second". Everything else is a rule and goes in the description as a plain sentence: triggers, special interactions, restrictions, resets. All-rules cards have no stat rows (NoStats); all-stats cards have no description. No flavour text, except on Hitscan, Onceler, MTG Player, Icon of Sin, Mahoraga and The Kirk Special - the last two replace their rules text with the joke on purpose.
  • Unless a player turns on card stat numbers in the game's options, a stat row shows its SimpleAmount word ("A bunch more", "Lower") instead of the amount. Pick the word that reads correctly after the stat name, or leave it unset to always show the amount — needed for anything a word can't say, like "+5% per second" or "Reversed".

Effects that change during a fight

Prefer scaling a stat where the game reads it over writing to the stored value. ApplyCardStats multiplies into the current value when cards are re-applied, and other mods' temporary effects add to and subtract from the same fields, so a live write that is "undone" later can leave the stat permanently wrong. The read points used here:

  • damage: HealthHandler.CallTakeDamage, on the one client that sends each hit (DamageMath). Never ProjectileHit.damage — the game reuses that field for bounce damage, poison and on-hit explosions on every client;
  • shot cooldown: Gun.usedCooldown; reload time: GunAmmo.ReloadTime();
  • movement speed: PlayerMovement.Move, scaled for the call and put straight back;
  • bullet speed: each bullet's MoveTransform.localForce, as it spawns.

Max health is read too widely to intercept, so Icon of Sin writes it relative to whatever is there and takes it back out when the fight ends. Components that track a fight derive from FightScoped, which handles the fight ending by death, by the point ending, and by a full stat reset.

Damage that depends on positions or timers must be worked out once, by the client that sends it. Every client applies the broadcast number and nothing ever resyncs health, so a calculation repeated on each machine drifts apart for good.

Card art

All 68 cards have art.

Images live in art/ and are embedded into the DLL at build time, so the mod still ships as a single file with no AssetBundle and no Unity project. At startup CardArt decodes the bytes into a Texture2D, wraps it in a Sprite, and builds a RectTransform

  • Image object — which is what CardVisuals.Start expects, since it instantiates CardInfo.cardArt as a child of the card's Canvas/Front/Background/Art transform.

To add art for another card:

  1. Drop a PNG or JPG in art/. Unity's Texture2D.LoadImage decodes nothing else — WebP, GIF and BMP all fail silently at runtime, so convert first.
  2. Add a line to resourceByCard in src/Util/CardArt.cs mapping the card's title to the filename. The key must match GetTitle() exactly or the card just stays blank.
  3. dotnet build.

Keep source images at roughly 512px on the long edge. Prefer JPG unless the image genuinely uses transparency — a PNG whose alpha channel is entirely opaque is several times larger for an identical result. The art is drawn with preserveAspect, so any aspect ratio letterboxes rather than distorting.

Known caveats

These are design limits, not bugs.

Prestige and Xane's Wire Management are the riskiest cards here. They're the only ones that rebuild a player's deck rather than change their stats, which they do through ModdingUtils. Each waits a frame after being picked before touching the deck. Worth testing on their own.

Infinity freezes enemy bullets that come within 2.5 units of the holder and releases them if the holder moves away. A frozen bullet is not sweeping for collisions, so walking into one does nothing. Radius is a knob in MoveTransform_Update_Patch. A standard bullet has no visible body - its sprite is disabled in the prefab and all you see is a trail that lives 0.03 seconds - so a frozen bullet is told to keep its trail, and a slow one too short to see gets a small tail drawn behind it. The bullet's own 15-second despawn timer is also held while it is frozen, so a long freeze can't destroy it mid-air.

Black Hole applies its pull only to players the local client owns, from the holder's synced position. That keeps it from fighting the network sync, at the cost of each client computing its own share. Radius and strength are knobs in BlackHole.

Tree pins the player to the first floor they land on each point, on the owning client only. Anything that moves them — knockback, a teleport card, a dash — gets undone on the next physics step. Walking is scaled to 0.001, not 0, to avoid feeding a zero into movement code that may divide by it, and jumps are refused outright. None of this is baked into the card's stats: every part checks live whether the player also holds The Lorax, which lifts all of it for as long as they hold it, in whichever order the two were picked.

The Kirk Special borrows an explosion prefab from the vanilla card pool at runtime (Supernova's, else Explosive Bullet's). Explosions deal damage through a broadcast RPC, so the visual is spawned on every client and only the attacker's own client keeps the damage. If no explosion prefab is found it falls back to plain area damage with no visual. Damage is 2.5x the holder's bullet damage in a 4-unit radius — both knobs on the card. One explosion per bullet hit, so shotguns and spray guns stack them. Only bullet hits schedule explosions; the explosions themselves never do.

MTG Player detects contact by overlapping collider bounds rather than collision callbacks, because ROUNDS moves players with kinematic physics. One hit per touch; the victim has to separate and come back.

Killdozer is calibrated by KilldozerContact.ReferenceSpeed, the speed a player has at 150% movement speed. At that speed with a default gun, one touch does exactly a full default health bar. The number is a guess until measured in-game; adjust it if one-taps land too early or too late.

Portal Gun teleports to the mouse cursor. It does not check whether the destination is inside a wall. Controller players fall back to six units along their aim direction.

Icon of Sin's "-5% reload speed" is implemented as reload time shrinking 5%/s, matching the "stronger over time" intent.

Legendary is RarityLib's built-in tier: a yellow border, and a draw weight of 0.025 against Rare's 0.1, Uncommon's 0.4 and Common's 1 - a quarter as common as Rare. This mod puts twelve cards there. Xane's Wire Management swaps a Legendary for another Legendary.

Random Crits rolls once per bullet, so guns that fire several projectiles per shot get several rolls per trigger pull. The roll happens on the client that owns the bullet, behind the same hasControl flag the game uses, so all clients agree on who died.

The Last Breath makes you immune to everything while the window is open, including self-damage and damage over time. If you haven't won by the time the five seconds are up, it kills you outright. Winning the point resets it before the timer can fire.

Chris's Side Project and The Last Breath both end in a self-kill sent straight through the game's damage relay, which ignores blocking, so a well-timed shield cannot dodge it. Last Breath's invincibility window would still swallow Chris's deadline if both were active at once.

Go Blue works by briefly counting the dead holder as alive inside the game's TeamsAlive() / GetLastTeamAlive() scans for 2.5 seconds (the card says one). If the window closes with the point still open, the holder's death is announced a second time. Multi-team lobbies are handled, but the card was designed around 1v1 ties.

Touch the Wall counts 3 pick phases in which you were offered cards — a phase where you don't pick (you won the round) doesn't use one up. It offers the rarest tier you can still be offered: Legendary, until every Legendary you could take is already in the hand or in your deck, then the next tier down. It works with and without CardChoiceSpawnUniqueCardPatch.

Daedalus Stormbow places each bullet at the top edge of the arena (y = 20), up to 3 units to either side of the cursor at random, pointed at the cursor. Only the shooter's client knows its cursor, so the spot is chosen there and travels with the bullet's own spawn message, which already carries position and rotation to everyone else. Controller players' "cursor" is six units along their aim. Every pellet of a multi-shot gun gets its own spot, so shotguns rain.

Patience stops other players' guns from firing for the first 3 seconds of each point, on each shooter's own machine, which is the only place a shot can start. The 3 seconds run on the holder's fight clock as each machine sees it, so the lock can end a few frames apart between players.

The Block Build Special fires block effects the same way the vanilla Empower card does: effects only, never the shield, once per trigger pull. Effects that behave differently for Empower (Teleport, Shield Charge) do the same here. Portal Gun teleports you on every shot.

Xane's Wire Management never draws itself or Prestige as a replacement. A card with no possible replacement is kept, and handed back without re-running its pick effects.

Mahoraga counts each hit once, even when Decay spreads it out. The reduction is worked out where the hit is sent, so everyone agrees on your health.

Conner's Pride genuinely checks the date — but at game start, since that's when SetupCard and GetStats run. It reads the local system clock.

Bird Killer zeroes remainingRespawns for the duration of a killing blow, which also suppresses any other respawn-granting mod.

Balanced averages two quantities that aren't in the same units (health ~100, bullet 55), so on a fresh player it reads as lose a quarter of your health, gain about 40% damage.

Giga Drum Mag cannot literally grant +100 ammo — the game clamps magazines at 90.

Ammo is spent per bullet, not per shot, everywhere in ROUNDS: each pellet a gun fires costs a round. So Super Shotgun (13 pellets against 3 + 9 ammo) fires once per reload, and Atomic Bomb empties the whole magazine every shot, which is the point of it.

Atomic Bomb sets bullets per shot to max ammo for the length of each shot and puts the stat straight back, so the link follows every ammo change and bullet count cards stop mattering while you hold it.

Super Shotgun copies the vanilla Buckshot card's range limit, read from the game's own data: spread 0.5, drag 10, bullets removed after 0.2s, bullet speed x3.5 - with bullet speed x3.85 instead, for about 10% more range. Drag takes a fixed fraction of a bullet's speed each frame, so range is roughly speed over drag. It does not copy Buckshot's -60% damage.

C-Ram squares attack speed as a percentage of the default rate, live and last, after every other attack speed effect, so cards picked later are squared too. Below 100% that makes you slower: 80% becomes 64%. It stacks, and each copy squares again - three copies turn 150% into about 2,560% and 80% into about 17%. The squared cooldown never drops below 0.0001s, which is already a shot every frame, and it is left out of the game's screen-shake maths so a nearly-never-firing gun can't fling the camera.

Drone Strike adds the real vanilla Homing and Explosive Bullet cards to your deck, a frame after it is picked. They are found by name; if another mod removes them from the game, nothing is added. Drone Strike, The Island and Prestige are never drawn by Xane's Wire Management, since each changes the deck as it is picked.

The Island picks its 2 cards at random, never another copy of The Island. Online, only the host's pick happens, so everyone loses the same cards.

500 Cigarettes converts damage from the vanilla Toxic Cloud card's clouds, recognised by their prefab name, E_ToxicCloud. Those clouds also hit the player who made them, so a holder who also has Toxic Cloud heals off their own clouds. The slow still applies. The health loss is not damage: it doesn't flash, count as a hit for Mahoraga, or stop for a shield, and it pauses during The Last Breath's invulnerability. Both the loss and the cloud heal are decided on one client and broadcast, so every player sees the same health.

ASU Frat Leader compares actual body sizes, and the game grows a player with their max health as well as with size cards, so health cards count. Size ratio is your size over theirs: 10% bigger is 1.1, which is +110% damage. Anything not bigger - equal included - just scales damage by the ratio. Bullets only.

Projectile Dysfunction steers each bullet by an amount drawn from smooth random noise, keyed to the bullet's own random seed and how long it has been flying. The shooter sends that seed with every bullet, so every player watches each bullet take the same crazy path.

Rare Artifact cuts every piece of damage by 20% at the very last step, on every client - bullets, explosions, the map edge, poison. Instant kills are still instant kills.

Multiplayer. ROUNDS keeps health in sync only because every client applies the same broadcast damage. A few effects are still worked out on each machine separately, so they can differ by a hair between players:

  • Poison (vanilla) ticks on each machine from the bullet's base damage. Damage multipliers don't boost it and Mahoraga doesn't reduce it.
  • Icon of Sin's max health grows on each machine's own clock.
  • Claude's Choice checks its cooldown on each machine.
  • Banana's stand-still timer and heal run on each machine.
  • The Last Breath window opens and closes on each machine's own clock, so a hit landing right at an edge of the window can count on one machine and not another.

Testing

Launch ROUNDS through Thunderstore Mod Manager, not Steam directly - the profile's BepInEx is what loads your DLL. To get a specific card reliably, open MODS → Toggle Cards from the main menu, hit Toggle All to disable everything, then re-enable just the card you want; every pick will then offer it.

Logs land in the profile's BepInEx/LogOutput.log.

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