Daily Reward Roller
Daily Reward Roller
A weighted random reward device: press a button to roll a reward from a weighted table, with a per-player streak that gently improves the odds.
Original, compile-verified Verse by Bizanator (Biloxi Studios). Clean-room sample you can drop into a UEFN project and adapt.
# Daily Reward Roller — Biloxi Studios Inc.
#
# A self-contained reward device. When a player triggers it (e.g. opens a chest
# via a button), it rolls a RANDOM reward from a weighted table and grants it by
# enabling the matching item spawner. A simple per-session "streak" counter
# nudges the odds toward better rewards the more a player keeps interacting.
#
# Teaches: parallel @editable arrays as a weighted table, weighted random
# selection with GetRandomInt, a per-player counter in a map keyed by agent, and
# clean fallback handling. Persistence is intentionally left out to keep this a
# focused, drop-in sample (swap the streak map for saved data when you need it).
#
# Setup: RewardSpawners[i] is granted with probability proportional to
# RewardWeights[i]. Wire GrantButton's InteractedWithEvent to this device by
# placing the device and pressing the button — the device subscribes on begin.
using { /Fortnite.com/Devices }
using { /Verse.org/Random }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
log_daily_reward_roller := class(log_channel){}
daily_reward_roller := class<concrete>(creative_device):
Logger : log = log{Channel := log_daily_reward_roller}
# The button players press to claim a reward.
@editable
GrantButton : button_device = button_device{}
# Item spawners, one per reward. Index i pairs with RewardWeights[i].
@editable
RewardSpawners : []item_spawner_device = array{}
# Relative weights for each reward (higher = more likely). Same length as
# RewardSpawners. A weight of 0 means "never rolled normally".
@editable
RewardWeights : []int = array{50, 30, 15, 5}
# Per-player interaction streak (resets when the session ends).
var StreakByPlayer : [agent]int = map{}
OnBegin<override>()<suspends> : void =
if (RewardSpawners.Length <= 0):
Logger.Print("No RewardSpawners configured.", ?Level := log_level.Warning)
return
GrantButton.InteractedWithEvent.Subscribe(OnButtonPressed)
OnButtonPressed(Player : agent) : void =
Streak := BumpStreak(Player)
RewardIndex := RollReward(Streak)
if (Spawner := RewardSpawners[RewardIndex]):
Spawner.SpawnItem()
Logger.Print("Granted reward {RewardIndex} (streak {Streak}).")
else:
Logger.Print("Roll produced an invalid reward index.", ?Level := log_level.Warning)
# Increment and return this player's streak. New players start at 1.
BumpStreak<private>(Player : agent) : int =
Current := if (Value := StreakByPlayer[Player]) then Value else 0
Next := Current + 1
if (set StreakByPlayer[Player] = Next):
return Next
return Next
# Pick a reward index via weighted random selection. A higher streak shifts a
# little weight toward the LAST (best) reward, so loyal players trend upward.
RollReward<private>(Streak : int) : int =
Weights := AdjustedWeights(Streak)
var Total : int = 0
for (W : Weights):
set Total += W
if (Total <= 0):
# Degenerate table: fall back to a uniform pick.
return GetRandomInt(0, RewardSpawners.Length - 1)
# Roll a number in [1, Total] and walk the cumulative weights.
Roll := GetRandomInt(1, Total)
var Cumulative : int = 0
for (Index -> W : Weights):
set Cumulative += W
if (Roll <= Cumulative):
return Index
# Numerically unreachable, but Verse needs a return on every path.
return RewardSpawners.Length - 1
# Build the effective weight table for this roll. We add a small streak bonus
# to the best reward's weight (capped) so the bias stays gentle.
AdjustedWeights<private>(Streak : int) : []int =
var Result : []int = RewardWeights
BonusToBest := Min(Streak, 10) # at most +10 weight on the best reward
BestIndex := RewardWeights.Length - 1
if (Current := Result[BestIndex]):
if (set Result[BestIndex] = Current + BonusToBest):
return Result