Help with a RNG device not triggering numbers once then resetting.

So, the past couple of days I have been trying to get the RNG Device to trigger each number once, then reset once all of the numbers have been picked. I thought the “Reset on Game Start” setting on pick each number once would do the trick because my game has 25 rounds to it, but that did not work. After some looking I saw somewhere on reddit a user suggesting to use verse for anything RNG related, so I tried that.

As a complete beginner to anything coding I consulted the AI within UEFN to help generate potential verse devices that could help my situation. I tried “pick each number once randomly and remove it from the array and reset when all numbers in the index are exhausted”, “shuffle all of the numbers in the index and pick one number in order every round”, tired some methods with persistence, and a lot of other variations. But, I cannot seem to get things to work, and I have no idea whether it is because the game resets per round, so it resets the array every round or what.

To clarify, I need a verse device that activates automatically on round start, picks a number between 1 - 8, and that number cannot be picked again once all of the other numbers are exhausted. Also these numbers are represented by triggers. How can I achieve this?

Hello @BigSkinny9 how are you?

Indeed! What you need to do is to use Verse to implement that kind of random selection!

Here is the exact code you should use in order to avoid repetitive numbers:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

# Added Random module
using { /Verse.org/Random }

random_number := class(creative_device):

    # Maximum number value (range will be 1 to MaxNumber)
    @editable
    var MaxNumber : int = 8

    # Array that stores numbers that haven't been chosen yet
    var AvailableNumbers : []int = array{}

    # Array of trigger devices corresponding to each number
    @editable
    var Triggers : []trigger_device = array{}

    # Button device that triggers the random number generation
    @editable
    MyButton : button_device = button_device{}

    # Called when the device begins - sets up the button subscription and initializes available numbers
    OnBegin<override>()<suspends>:void=
        # Subscribe the GetRandomNumber function to the button interaction event
        MyButton.InteractedWithEvent.Subscribe(GetRandomNumber)
        # Initialize the array with all available numbers (1 to MaxNumber)
        InitializeAvailableNumbers()

    # Populates the AvailableNumbers array with values from 1 to MaxNumber
    InitializeAvailableNumbers() : void =
        # Create a temporary array to build the number list
        var TempArray : []int = array{}
        # Loop from 1 to MaxNumber and add each number to the array
        for (i := 1..MaxNumber):
            set TempArray += array{i}
        # Assign the completed array to AvailableNumbers
        set AvailableNumbers = TempArray

    # Generates a random number from the available pool and removes it
    GetRandomNumber(Agent:agent) : void =
        # If all numbers have been chosen, reset the available numbers pool
        if (AvailableNumbers.Length = 0):
            InitializeAvailableNumbers()

        # Generate a random index within the range of available numbers
        var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)
        
        # Get the number at the random index
        if (ChosenNum := AvailableNumbers[RandomIndex]):
            # Create a new array excluding the chosen number
            var NewAvailable : []int = array{}
            # Loop through all available numbers
            for (i -> Num : AvailableNumbers):
                # Add all numbers except the one at RandomIndex
                if (i <> RandomIndex):
                    set NewAvailable += array{Num}
            # Update AvailableNumbers with the new array (chosen number removed)
            set AvailableNumbers = NewAvailable
            
            # Process and display the chosen number
            ActivateTrigger(ChosenNum)

    # Displays the chosen number and remaining available numbers count / You can replace this function to fit your needs
    ActivateTrigger(RandomNum:int) : void =
        Print("----------------------Chosen Number: {RandomNum}")
        Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")

        # Activate the corresponding trigger based on the chosen number
        if(TriggerToActivate := Triggers[RandomNum - 1]):
            TriggerToActivate.Trigger()

Off course you can change the way of triggering ithis device, I’m using a button for testing purposes only.

As an additional comment, your Triggers array’s length must be the same as your MaxNumber (8 triggers for your 8 MaxNumber in this case)

Hope this helps you!!!

Hey @BRGJuanCruzMK, I am good! Thank you for reaching out, I implemented the verse and made it so a trigger device activates it on game start instead of the button. Unfortunately, I am still getting repeated numbers across rounds (Ex. Index 2 triggering on both Round 2 & 5). I have the verse enabled at game start, and I filled indexs 0-7 with the appropriate triggers. Does it repeat because I am testing it through a session on UEFN? Or is there something else I am missing entirely? Here is the code I have in case: using { /Fortnite.com/Devices }

using { /Verse.org/Simulation }

using { /UnrealEngine.com/Temporary/Diagnostics }

using { /Verse.org/Random }

# Device that generates random numbers and triggers corresponding devices

random_number := class(creative_device):

\# Maximum number value (range will be 1 to MaxNumber)

@editable

var MaxNumber : int = 8



\# Array that stores numbers that haven't been chosen yet

var AvailableNumbers : \[\]int = array{}



\# Array of trigger devices corresponding to each number

@editable

var Triggers : \[\]trigger_device = array{}



\# Main trigger device that starts the random number generation

@editable

MainTrigger : trigger_device = trigger_device{}



\# Called when the device begins - sets up the trigger subscription and initializes available numbers

OnBegin<override>()<suspends>:void=

    \# Subscribe the GetRandomNumber function to the trigger's TriggeredEvent

    MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)

    \# Initialize the array with all available numbers (1 to MaxNumber)

    InitializeAvailableNumbers()



\# Populates the AvailableNumbers array with values from 1 to MaxNumber

InitializeAvailableNumbers() : void =

    \# Create a temporary array to build the number list

    var TempArray : \[\]int = array{}

    \# Loop from 1 to MaxNumber and add each number to the array

    for (i := 1..MaxNumber):

        set TempArray += array{i}

    \# Assign the completed array to AvailableNumbers

    set AvailableNumbers = TempArray



\# Generates a random number from the available pool and removes it

GetRandomNumber(Agent:?agent) : void =

    \# If all numbers have been chosen, reset the available numbers pool

    if (AvailableNumbers.Length = 0):

        InitializeAvailableNumbers()



    \# Generate a random index within the range of available numbers

    var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)

    

    \# Get the number at the random index

    if (ChosenNum := AvailableNumbers\[RandomIndex\]):

        \# Create a new array excluding the chosen number

        var NewAvailable : \[\]int = array{}

        \# Loop through all available numbers

        for (i -> Num : AvailableNumbers):

            \# Add all numbers except the one at RandomIndex

            if (i <> RandomIndex):

                set NewAvailable += array{Num}

        \# Update AvailableNumbers with the new array (chosen number removed)

        set AvailableNumbers = NewAvailable

        

        \# Process and display the chosen number

        ActivateTrigger(ChosenNum)



\# Displays the chosen number and remaining available numbers count / You can replace this function to fit your needs

ActivateTrigger(RandomNum:int) : void =

    Print("----------------------Chosen Number: {RandomNum}")

    Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")



    \# Activate the corresponding trigger based on the chosen number

    if(TriggerToActivate := Triggers\[RandomNum - 1\]):

        TriggerToActivate.Trigger()

Hello again @BigSkinny9

As you are using a Trigger device to get the random number, you are getting a “multiple subscriptions” issue!

As the “OnBegin” funcions is called every start of the round, you are subscribing multiple times to that trigger, so it is triggering multiple times and deplenishing your available numbers array too quick, giving you repeated numbers!

There is an extremely easy way to solve this issue! You need to add a IsSubscribed logic variable with its default value set on “false” and then check if it is false before subscribing to your trigger. It should look like this:

# Main trigger device that starts the random number generation
    @editable
    MainTrigger : trigger_device = trigger_device{}
    var IsSubscribed : logic = false

    # Called when the device begins - sets up the trigger subscription and initializes available numbers
    OnBegin<override>()<suspends>:void=
        # Subscribe the GetRandomNumber function to the trigger's TriggeredEvent
        if(IsSubscribed = false):
            MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)
            set IsSubscribed = true

This way, you will only subscribe to the trigger once per match, not per round! That is because you are setting the IsSubscribed variable to “true” immediately after subscribing to your trigger, avoiding future subscriptions as the condition to subscribe is that variable being “false”

Hope this helps you!

Hey @BRGJuanCruzMK, I am still running into the same issue of getting repeat numbers :confused:

I tried setting up a timer to go into the trigger to see if that was the issue and I also tried a round settings device to trigger that device on round 1, but both didn’t help. Here are some screenshots of the trigger device and the verse device if that helps, and the updated code I used. Just to clarify, I wanted to use this verse as like a random loot pool selector whenever a round begins, but of course that loot pool cannot be selected again until all of the other ones have been selected. So like trigger on round start → verse → random loot pool for the round, if that makes sense? And by like round nine the numbers are reset?

using { /Fortnite.com/Devices }

using { /Verse.org/Simulation }

using { /UnrealEngine.com/Temporary/Diagnostics }

using { /Verse.org/Random }

# Device that generates random numbers and triggers corresponding devices

random_number := class(creative_device):

\# Maximum number value (range will be 1 to MaxNumber)

@editable

var MaxNumber : int = 8



\# Array that stores numbers that haven't been chosen yet

var AvailableNumbers : \[\]int = array{}



\# Array of trigger devices corresponding to each number

@editable

var Triggers : \[\]trigger_device = array{}



\# Main trigger device that starts the random number generation

@editable

MainTrigger : trigger_device = trigger_device{}



\# Track if we've already subscribed to the trigger

var IsSubscribed : logic = false



\# Called when the device begins - sets up the trigger subscription and initializes available numbers

OnBegin<override>()<suspends>:void=

    \# Only subscribe once per match

    if (IsSubscribed = false):

        MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)

        set IsSubscribed = true

    \# Initialize the array with all available numbers (1 to MaxNumber)

    InitializeAvailableNumbers()



\# Populates the AvailableNumbers array with values from 1 to MaxNumber

InitializeAvailableNumbers() : void =

    \# Create a temporary array to build the number list

    var TempArray : \[\]int = array{}

    \# Loop from 1 to MaxNumber and add each number to the array

    for (i := 1..MaxNumber):

        set TempArray += array{i}

    \# Assign the completed array to AvailableNumbers

    set AvailableNumbers = TempArray



\# Generates a random number from the available pool and removes it

GetRandomNumber(Agent:?agent) : void =

    \# If all numbers have been chosen, reset the available numbers pool

    if (AvailableNumbers.Length = 0):

        InitializeAvailableNumbers()



    \# Generate a random index within the range of available numbers

    var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)

    

    \# Get the number at the random index

    if (ChosenNum := AvailableNumbers\[RandomIndex\]):

        \# Create a new array excluding the chosen number

        var NewAvailable : \[\]int = array{}

        \# Loop through all available numbers

        for (i -> Num : AvailableNumbers):

            \# Add all numbers except the one at RandomIndex

            if (i <> RandomIndex):

                set NewAvailable += array{Num}

        \# Update AvailableNumbers with the new array (chosen number removed)

        set AvailableNumbers = NewAvailable

        

        \# Process and display the chosen number

        ActivateTrigger(ChosenNum)



\# Displays the chosen number and remaining available numbers count / You can replace this function to fit your needs

ActivateTrigger(RandomNum:int) : void =

    Print("----------------------Chosen Number: {RandomNum}")

    Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")



    \# Activate the corresponding trigger based on the chosen number

    if(TriggerToActivate := Triggers\[RandomNum - 1\]):

        TriggerToActivate.Trigger()

Hello again!

Ok, I was completely sure the variables had some kind of persistence between rounds… Spoiler alert: I was wrong…

So I had to change the approach and use real persistence for the variables. Most of the code remains the same, but now I’m storing the AvailableNumbers array and the IsInitialized logic variable into a persitable class instead as putting them directly inside our main class.

If you want to know more about persistence I highly recommend you to watch this list of videos.

But here is the new code:

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }

# ==================================================================================
# GLOBAL PERSISTENCE MAP - DO NOT MOVE OR RENAME
# ==================================================================================
# This weak_map stores NumberManager data for each player across rounds
# weak_map allows automatic cleanup when players leave
# NEVER MOVE THIS TO A DIFFERENT FILE OR FOLDER
# NEVER CHANGE THE VARIABLE NAME
var NumberManagerMap : weak_map(player, NumberManager) = map{}

# ==================================================================================
# NUMBER MANAGER CLASS - PERSISTABLE DATA STRUCTURE
# ==================================================================================
# NEVER MOVE THIS TO A DIFFERENT FILE OR FOLDER
# NEVER CHANGE THE VARIABLE NAMES OR THE CLASS NAME BELOW
# The <persistable> specifier makes this class's data survive round resets
# <final> prevents inheritance from this class
NumberManager := class<final><persistable>:
    # Stores the list of numbers that haven't been selected yet
    AvailableNumbers : []int = array{}
    # Flag to track if this manager has been initialized for a player
    IsInitialized : logic = false

# ==================================================================================
# CONSTRUCTOR FOR NUMBER MANAGER
# ==================================================================================
# Creates a new NumberManager instance with copied data
# <transacts> indicates this function modifies game state
MakeNumberManager<constructor>(NM:NumberManager)<transacts>:=NumberManager:
    AvailableNumbers := NM.AvailableNumbers
    IsInitialized := NM.IsInitialized

# ==================================================================================
# PLAYER EXTENSION FUNCTIONS - THESE SHOULD BE OUTSIDE ANY CLASS
# ==================================================================================
# Gets the NumberManager data associated with a specific player
# Returns existing data if found, otherwise creates new instance
(Player:player).GetNumberManager():NumberManager=
    # Safety check: ensure player is still active in the game
    if(not Player.IsActive[]): # Avoid crashes
        return NumberManager{}
    # Check if player already has a NumberManager in the map
    if(NM:=NumberManagerMap[Player]):
        return NM
    else:
        # Create a new NumberManager for this player
        NewNumberManager:=NumberManager{}
        # Attempt to store it in the map
        if. set NumberManagerMap[Player] = NewNumberManager
        else. Print("Error: Failed to get Number Manager data")
        return NewNumberManager

# Updates the NumberManager data for a specific player
# This saves the current state to the persistent map
(Player:player).SetNumberManagerData(NewNumberManager:NumberManager):void=
    # Safety check: ensure player is still active in the game
    if(not Player.IsActive[]): # Avoid crashes
        return
    # Attempt to update the player's data in the map
    if. set NumberManagerMap[Player] = NewNumberManager
    else. Print("Error: Failed to set Number Manager data")

# ==================================================================================
# MAIN DEVICE CLASS - RANDOM NUMBER GENERATOR
# ==================================================================================
# Device that generates random numbers and triggers corresponding devices
random_number := class(creative_device):

    # ================== EDITABLE PROPERTIES ==================
    # Maximum value for random number generation (range: 1 to MaxNumber)
    @editable
    MaxNumber : int = 8

    # ================== RUNTIME STATE VARIABLES ==================
    # Current list of available numbers (synced from player's NumberManager)
    var AvailableNumbers : []int = array{}
    # Flag to track initialization status
    var IsInitialized : logic = false

    # Array of trigger devices, one for each possible number (1 to MaxNumber)
    @editable
    var Triggers : []trigger_device = array{}

    # The trigger device that initiates random number generation
    @editable
    MainTrigger : trigger_device = trigger_device{}

    # ================== INITIALIZATION ==================
    # Called when the device is initialized (at match start and each round)
    OnBegin<override>()<suspends>:void=
        # Get all players currently in the game
        Players := GetPlayspace().GetPlayers()
        # Initialize data for each existing player
        for (Player : Players):
            InitializePlayerData(Player)
        
        # Subscribe to event that fires when new players join mid-game
        GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
        # Subscribe to the main trigger's activation event
        MainTrigger.TriggeredEvent.Subscribe(GetRandomNumber)

    # ================== PLAYER DATA INITIALIZATION ==================
    # Sets up or retrieves existing data for a player
    InitializePlayerData(Player : player):void =
        # Get the player's persistent NumberManager data
        NM:=Player.GetNumberManager()
        # Copy data from persistent storage to local variables
        set AvailableNumbers = NM.AvailableNumbers
        set IsInitialized = NM.IsInitialized
        # If this is the first time, populate the available numbers
        if(IsInitialized = false):
            InitializeAvailableNumbers(AvailableNumbers)
            set IsInitialized = true

    # ================== NEW PLAYER HANDLER ==================
    # Called when a new player joins the game mid-match
    OnPlayerAdded(Player : player):void =
        # Get the new player's NumberManager (creates new if doesn't exist)
        NM:=Player.GetNumberManager()
        # Create a new NumberManager with current game state
        NewNM:=NumberManager:
            AvailableNumbers := AvailableNumbers
            IsInitialized := IsInitialized
            MakeNumberManager<constructor>(NM)
        # Save this data to the player's persistent storage
        Player.SetNumberManagerData(NewNM)

    # ================== NUMBER POOL INITIALIZATION ==================
    # Fills the available numbers array with values from 1 to MaxNumber
    InitializeAvailableNumbers(AuxAvailableNumbers:[]int) : void =
        # Create a temporary array to build the number list
        var TempArray : []int = array{}
        # Loop from 1 to MaxNumber and add each number to the array
        for (i := 1..MaxNumber):
            set TempArray += array{i}
        # Assign the completed array to AvailableNumbers
        set AvailableNumbers = TempArray
        
    # ================== POST-GENERATION SYNC ==================
    # Syncs the current state to all players' persistent data after number selection
    OnRandomNumberGenerated():void=
        # Get all active players
        Players := GetPlayspace().GetPlayers()
        # Update persistent data for each player
        for (Player : Players):
            # Get player's current NumberManager
            NM:=Player.GetNumberManager()
            # Create updated NumberManager with new state
            NewNM:=NumberManager:
                AvailableNumbers := AvailableNumbers
                IsInitialized := IsInitialized
                MakeNumberManager<constructor>(NM)
            # Save updated state to persistent storage
            Player.SetNumberManagerData(NewNM)

    # ================== RANDOM NUMBER GENERATION ==================
    # Generates and removes a random number from the available pool
    GetRandomNumber(Agent:?agent) : void =
        # If all numbers have been used, refill the pool
        if (AvailableNumbers.Length = 0):
            InitializeAvailableNumbers()
        
        # Generate a random index within the available numbers array
        var RandomIndex : int = GetRandomInt(0, AvailableNumbers.Length - 1)
        
        # Retrieve the number at the random index
        if (ChosenNum := AvailableNumbers[RandomIndex]):
            # Create a new array without the chosen number
            var NewAvailable : []int = array{}
            # Loop through all available numbers
            for (i -> Num : AvailableNumbers):
                # Add all numbers except the one at RandomIndex
                if (i <> RandomIndex):
                    set NewAvailable += array{Num}
            
            # Update AvailableNumbers with the chosen number removed
            set AvailableNumbers = NewAvailable
            # Trigger the corresponding device and display info
            ActivateTrigger(ChosenNum)
            # Sync the updated state to all players' persistent data
            OnRandomNumberGenerated()

    # ================== TRIGGER ACTIVATION ==================
    # Activates the trigger device corresponding to the chosen number
    # You can replace this function to fit your needs
    ActivateTrigger(RandomNum:int) : void =
        # Print the chosen number to the console
        Print("----------------------Chosen Number: {RandomNum}")
        # Print how many numbers are still available
        Print("----------------Number of Available Numbers: {AvailableNumbers.Length}")
        
        # Activate the trigger device at index (RandomNum - 1)
        # Arrays are 0-indexed but our numbers start at 1
        if(TriggerToActivate := Triggers[RandomNum - 1]):
            TriggerToActivate.Trigger()

As you can see, I added a weak_map, 2 new classes, and 2 new functions that are outside any class. All those things are explained in the playlist I shared.

In addition, I added new funcions inside our main class to handle those changes and a lot of comments and separators so you can read an understand the code.

With this changes, you should be able to get random numbers without any repetition until you run out of options!

Let me know if you need more help!

Hey again!

Just tested it and it worked! Honestly extremely excited that it finally worked. I watched the videos you sent and they were pretty informative! I noticed that persistence data is per player only, and there isn’t such thing as global persistence? Would that mess things up in a multiplayer game? Also, if I were to change the amount of numbers in the array in the future, would I remove every instance of “8” and replace it with like 7 or 9 for example? Last thing, if I were to use this with another set of triggers and trigger that triggers the device, could I use the same code? Or should I create a new verse file and copy and paste the code?

Thank you so much @BRGJuanCruzMK!!