CityRP Documentation Repository

LimeCasino deployment math

Last updated

How CityRP’s pCasino machines are tuned, how to place them, and how players read the odds.

In-universe, the operator is LimeCasino: see the lore page.

Canonical machine configs live in addons/pcasino/presets/*.json. Runtime copies are generated into addons/pcasino/lua/perfectcasino/config/sh_presets.lua by the math toolkit. Do not hand-edit the Lua file.

Targets

Machine Bet Volatility Cash RTP
Classic Slots — Low $500 low-medium 95%
Classic Slots — Mid $1,000 low-medium 95%
Classic Slots — High $2,500 medium 95%
Wheel Slots — Low $5,000 medium 95%
Wheel Slots — Mid $10,000 medium-high 95%
Wheel Slots — High $25,000 medium-high 95%
Mystery Wheel free spin only high n/a (prize wheel)

Cash RTP is expected cash returned per dollar wagered over the long run, including:

  • line / pair pays
  • jackpot pot payouts (player betAdd contributions and the startValue re-seed pCasino injects after every jackpot hit)
  • mini-wheel cash and mini-wheel jackpots
  • the cash expected value of Mystery Wheel spins earned as mini-wheel prizes

Non-tradable item prizes (cars, Dragon’s Breath, etc.) are $0 toward cash RTP. They are prestige odds, tracked separately.

House edge on cash is therefore ~5%. Items do not inflate the money supply when won.

How pCasino RNG actually works

There are no reel strips and no built-in RTP field.

  1. Slots build a weighted bag from chance weights and pick three symbols independently (table.Random).
  2. Combos are matched with “best pay / jackpot preferred” rules in CheckForCombo.
  3. Cash line payout is bet * (1 + p) (stake returned plus profit multiplier p).
  4. Mini-wheel (advanced) and Mystery Wheel are uniform: math.random(12) / math.random(20). Rarity = fewer segments of that prize, not weighted slices.

So “higher bet → better chance at a Big Wheel spin” is done by putting more prize_wheel segments on that machine’s mini-wheel (1 / 2 / 3 of 12), with chest rates climbing modestly as well.

Mystery Wheel layout (20 × 5%)

Free spins only (buySpin.buy = false).

Kind Count Notes
Nothing 4
Cash 9 $1 … $100,000
Spin Again 1 recursive; raises cash EV slightly
Consolation items 2 Trabbi, Golden Vape
Grand prizes 3 Dragon’s Breath, BMW M3 GTR, Kawasaki Ninja
Extra car 1 BMW i8

Expected cash from one spin is about $10,211 (items excluded). That figure is folded into advanced-slot RTP when a mini-wheel awards prize_wheel.

Grand prize odds on the wheel itself: 5% per named segment (1 in 20). Getting onto the wheel is the scarce part — see each advanced machine’s “Big Wheel chance / spin” in /odds.

Mini-wheel design

Advanced machine Big Wheel segments Jackpot segments Rest
$5,000 1/12 1/12 cash + nothing
$10,000 2/12 1/12 cash + nothing
$25,000 3/12 1/12 cash + nothing

Mini-wheel cash amounts are multiples of that machine’s bet. Items stay on the big wheel so the prize hierarchy stays clear.

Jackpot startValue footgun

After a jackpot pays, pCasino resets the pot to startValue. That seed is new money created each cycle, not player contributions. Exact RTP must include P(jackpot award) × startValue / bet on top of the betAdd contribution rate. Ignoring it makes advanced machines look “95%” on paper while simulating well over 100% cash return.

Presets keep startValue modest (about 5× bet on classic, 1× bet on wheel slots) so the seed does not dominate.

Placing machines (staff)

  1. Equip the pCasino toolgun → right-click to open the config menu.
  2. Pick the entity type (Slot Machine / Wheel Slot Machine / Mystery Wheel).
  3. Open Load LimeCasino Preset and click the matching preset.
  4. Left-click to place. Settings are saved into pcasino_ents as usual.

JSON under addons/pcasino/presets/ is the hand-config recipe if you ever need to type values manually.

Player transparency

/odds opens a browser of the published presets: bet, volatility, cash RTP, paylines with probabilities, mini-wheel segments, and Mystery Wheel segments. It reads the same data the toolgun loads — not live entity drift — so what players see matches what we intended to deploy.

Proving the math

Player-facing guide (Lua ↔ Python mapping, item hunts, FAQ):

addons/pcasino/tools/README.md

From that folder:

python3 casino_math.py --spins 500000
python3 casino_math.py --hunt bmwm3gtr46 --skip-rtp
python3 casino_math.py --list-items

The toolkit:

  • enumerates every weighted reel outcome (exact RTP / hit rate)
  • folds mini-wheel + Mystery Wheel cash EV into advanced RTP
  • Monte Carlo–checks cash return and house net
  • --hunt <item_id> — expected spins / $ wagered / net cash to a Mystery prize on each advanced machine
  • runs a rough multi-machine floor stress mix

Regenerate sh_presets.lua whenever you edit a JSON preset:

python3 casino_math.py --emit-lua ../lua/perfectcasino/config/sh_presets.lua --skip-rtp

Hunt equalization (advanced machines)

Single-segment Mystery items (M3 GTR, Ninja, Dragon’s Breath, Rolex, Police Uniform, i8, …) are tuned so **expected cash cost is the same** on Adv Low / Mid / High ($7.5M wagered, ~$375k expected net loss at 95% RTP). Magical Cake has two wheel segments, so it is roughly twice as likely. Per-spin odds still rise with bet (1 → 2 → 3 Big Wheel mini-segments, plus chest rate).

Casino-granted i8 / Rolex / Police Uniform / Magical Cake stacks carry hidden tradable=false metadata and cannot be given, dropped, or inventory-sold. Trabbi stays tradable.

Economy notes

  • Cars / Dragon’s Breath are bound / non-tradable — flashy without a cash secondary market.
  • Mystery Wheel cash is real money; it is budgeted inside the 95% advanced RTP, not stacked on top.
  • Expect session variance: published RTP is long-run. Short samples on high-volatility wheel slots will swing around 95%.
  • Access tag P and /selfexclude still gate play — but only new bets. An advanced machine's pending Big Wheel spin (won on a bet placed before an exclusion landed) can still be claimed; pcasino_wheel_slot_machine:Use() lets that claim through before the bet-gate check runs.

Session gambling monitoring

gamemode/core/plugins/pcasino/sv_init.lua keeps two per-sitting tallies for every player, fed by pCasinoMoneyChanged (all machine types, since every cash movement routes through PerfectCasino.Config.AddMoney):

  • Net loss (cityrp.pcasino.SessionNet) — stakes minus payouts, floored at zero. What the player is actually down.
  • Turnover (cityrp.pcasino.SessionWagered) — everything staked, however it resolved. Catches the long grinder whom variance kept afloat.

Non-cash prizes (Mystery Wheel items) move neither tally, so a player who wins an expensive item still reads as down whatever cash they staked for it.

  • Warning tiers (cityrp.pcasino.WarningTiers) escalate on a single shared ladder that either tally can advance: $100k net loss or $2M staked → in-game reminder pointing at /selfexclude; $200k / $4M → second reminder, which also logs to the admin hub. Each tier fires once per session and the notice names whichever tally actually tripped. The loss floor sits well above a single top-tier spin (advanced_high stakes $25,000) so one unlucky bet never reads as a loss pattern — tier 1 needs 4 full losses back to back.
  • Auto-exclusion tiers (cityrp.pcasino.AutoExcludeTiers) escalate on their own single shared ladder the same way: $250k net loss or $5M staked → 15 minutes; $750k / $15M → 1 hour; $1.25M / $25M → 24 hours (played time). Each fires via the same cityrp.access.selfTakeAccess path /selfexclude uses and alerts staff. Neither tally is reset when a tier fires, so a player who keeps playing once a short exclusion lifts escalates straight to the next tier instead of restarting the clock.

Both ladders are shared rather than per-tally, so a player can be notified at most twice by warnings and excluded at most three times total — reaching a tier via loss and later via turnover doesn't fire it twice.

The turnover thresholds sit at 20× their matching loss thresholds, which is roughly where an average-luck player at the published 95% RTP would be down the loss figure — so on typical luck the two ladders reach a tier at about the same time, the loss ladder fires first for an unlucky player, and the turnover ladder still eventually catches a lucky one.

Both tallies reset on disconnect — this is a same-sitting safeguard, not a persistent affordability check. Tune the thresholds if machine bets/RTP change enough that normal variance starts tripping them.