VerseIsland
Feed Learn Leaderboard Guides Reference Explore Search Docs Verse Library
Sign in
Feed Learn 📖 Codex Leaderboard Guides Reference Explore Search Docs Verse Library
Verse Library › Materials/VFX
Browse Verse Library

Library

Devices 634
  • Devices166
  • Triggers & Logic117
  • UI & HUD41
  • Audio39
  • Items & Inventory29
  • Events28
  • Scoring & Objectives24
  • Spawning19
  • Classes & Interfaces18
  • Game Modes16
  • Props & Meshes13
  • Variables & Mutability12
  • Modules & Structure11
  • Player Management11
  • Round Flow9
  • Tutorials7
  • Animation6
  • Functions6
  • Getting Started6
  • Teams & Classes6
  • Concurrency5
  • Debugging5
  • NPCs & AI5
  • VFX & Particles5
  • Cameras4
  • Arrays & Maps3
  • Control Flow3
  • Structs & Enums3
  • Syntax & Types3
  • Cinematics2
  • Persistence2
  • Scene Graph2
  • Building1
  • Other1
  • Physics1
  • Publishing & Islands1
  • Reference1
  • Templates1
  • Vehicles1
  • Weapons1
Verse Language 257
  • Control Flow35
  • Functions27
  • Arrays & Maps25
  • Classes & Interfaces22
  • Variables & Mutability22
  • Concurrency21
  • Syntax & Types18
  • Modules & Structure12
  • Debugging8
  • Devices8
  • Events7
  • Player Management6
  • Structs & Enums6
  • Triggers & Logic6
  • Failure & Options4
  • Game Modes4
  • UI & HUD4
  • Getting Started3
  • Round Flow3
  • Scene Graph3
  • Spawning3
  • Reference2
  • Templates2
  • VFX & Particles2
  • Animation1
  • Best Practices1
  • Cameras1
  • Props & Meshes1
Materials/VFX 154
  • Triggers & Logic27
  • UI & HUD17
  • Devices16
  • VFX & Particles16
  • Game Modes8
  • Props & Meshes8
  • Scoring & Objectives8
  • Items & Inventory7
  • Arrays & Maps5
  • Events4
  • Animation3
  • Cinematics3
  • Classes & Interfaces3
  • Control Flow3
  • Modules & Structure3
  • Round Flow3
  • Spawning3
  • Concurrency2
  • Functions2
  • Reference2
  • Scene Graph2
  • Syntax & Types2
  • Variables & Mutability2
  • Getting Started1
  • Persistence1
  • Player Management1
  • Structs & Enums1
  • Tutorials1
General 153
  • Devices22
  • Events20
  • Triggers & Logic20
  • Classes & Interfaces10
  • Variables & Mutability8
  • Round Flow6
  • Scoring & Objectives6
  • UI & HUD6
  • Functions5
  • Player Management5
  • Game Modes4
  • Getting Started4
  • Props & Meshes4
  • Control Flow3
  • Syntax & Types3
  • VFX & Particles3
  • Arrays & Maps2
  • Best Practices2
  • NPCs & AI2
  • Other2
  • Physics2
  • Scene Graph2
  • Spawning2
  • Structs & Enums2
  • Tutorials2
  • Building1
  • Cameras1
  • Cinematics1
  • Failure & Options1
  • Items & Inventory1
  • Teams & Classes1
Player 133
  • Player Management22
  • Teams & Classes18
  • Classes & Interfaces12
  • Devices11
  • Triggers & Logic9
  • Events8
  • Persistence7
  • Arrays & Maps6
  • Game Modes5
  • Round Flow4
  • Functions3
  • Getting Started3
  • Items & Inventory3
  • Modules & Structure3
  • Concurrency2
  • Control Flow2
  • Debugging2
  • Props & Meshes2
  • Scoring & Objectives2
  • Spawning2
  • Structs & Enums2
  • Physics1
  • Reference1
  • UI & HUD1
  • Variables & Mutability1
  • Weapons1
Building & Props 131
  • Variables & Mutability15
  • Props & Meshes13
  • Triggers & Logic13
  • Classes & Interfaces10
  • Devices10
  • UI & HUD10
  • Events7
  • Teams & Classes7
  • Functions6
  • Animation5
  • Building5
  • VFX & Particles5
  • Structs & Enums4
  • Control Flow3
  • Game Modes3
  • Items & Inventory3
  • Round Flow3
  • Scene Graph3
  • Concurrency2
  • Reference2
  • Arrays & Maps1
  • Modules & Structure1
NPCs & AI 76
  • NPCs & AI34
  • Spawning7
  • Animation6
  • Variables & Mutability5
  • Classes & Interfaces3
  • Debugging3
  • Game Modes3
  • UI & HUD3
  • Audio2
  • Concurrency2
  • Props & Meshes2
  • Devices1
  • Functions1
  • Getting Started1
  • Reference1
  • Triggers & Logic1
  • VFX & Particles1
Spawning 73
  • Spawning31
  • Concurrency6
  • Events6
  • Vehicles5
  • Player Management4
  • Props & Meshes4
  • Items & Inventory3
  • Teams & Classes3
  • Devices2
  • Functions2
  • Round Flow2
  • Scene Graph2
  • Control Flow1
  • Scoring & Objectives1
  • Triggers & Logic1
Cinematics 71
  • Cameras17
  • Cinematics15
  • Devices11
  • Audio5
  • Animation4
  • Triggers & Logic4
  • Functions2
  • Game Modes2
  • Templates2
  • UI & HUD2
  • Classes & Interfaces1
  • Concurrency1
  • Control Flow1
  • Debugging1
  • Items & Inventory1
  • Modules & Structure1
  • Scene Graph1
UI 57
  • UI & HUD34
  • Devices5
  • Scoring & Objectives4
  • Classes & Interfaces3
  • Round Flow2
  • Scene Graph2
  • Arrays & Maps1
  • Functions1
  • Getting Started1
  • Reference1
  • Structs & Enums1
  • Triggers & Logic1
  • Variables & Mutability1
Scene Graph 40
  • Scene Graph9
  • Items & Inventory8
  • Classes & Interfaces3
  • Events3
  • Props & Meshes3
  • Reference2
  • Arrays & Maps1
  • Building1
  • Control Flow1
  • Debugging1
  • Functions1
  • Modules & Structure1
  • Player Management1
  • Structs & Enums1
  • Syntax & Types1
  • Triggers & Logic1
  • UI & HUD1
  • VFX & Particles1
Vehicles 38
  • Spawning12
  • Devices7
  • Vehicles7
  • Props & Meshes4
  • Game Modes2
  • Failure & Options1
  • Items & Inventory1
  • Reference1
  • Teams & Classes1
  • Tutorials1
  • VFX & Particles1
Tutorials 37
  • Game Modes6
  • Triggers & Logic5
  • Classes & Interfaces4
  • Events4
  • Props & Meshes3
  • Building2
  • Devices2
  • Items & Inventory2
  • Scoring & Objectives2
  • Animation1
  • Arrays & Maps1
  • Cameras1
  • Concurrency1
  • Physics1
  • Player Management1
  • Structs & Enums1
Weapons 30
  • Devices6
  • Game Modes6
  • Weapons4
  • Items & Inventory3
  • Scoring & Objectives3
  • Spawning2
  • Teams & Classes2
  • Best Practices1
  • Classes & Interfaces1
  • Functions1
  • Triggers & Logic1
Items 28
  • Items & Inventory23
  • Devices2
  • Triggers & Logic2
  • Spawning1
Assets 20
  • Modules & Structure6
  • Arrays & Maps3
  • Functions3
  • Scene Graph2
  • Best Practices1
  • Devices1
  • Events1
  • Failure & Options1
  • Physics1
  • Variables & Mutability1
Animation 19
  • Animation9
  • Variables & Mutability4
  • Devices1
  • Props & Meshes1
  • Reference1
  • Scene Graph1
  • Syntax & Types1
  • Tutorials1
Release Notes 17
  • Classes & Interfaces5
  • Modules & Structure3
  • Concurrency2
  • Control Flow2
  • Variables & Mutability2
  • Debugging1
  • Devices1
  • Items & Inventory1
Input & Controls 16
  • Functions3
  • Control Flow2
  • Syntax & Types2
  • UI & HUD2
  • Animation1
  • Arrays & Maps1
  • Cinematics1
  • Debugging1
  • Devices1
  • Events1
  • Reference1
Publishing 8
  • Control Flow5
  • Arrays & Maps1
  • Devices1
  • Syntax & Types1
Audio 7
  • Audio5
  • UI & HUD2
Landscape 7
  • Other4
  • VFX & Particles2
  • Scene Graph1
Persistence 6
  • Structs & Enums2
  • Items & Inventory1
  • Persistence1
  • Publishing & Islands1
  • Variables & Mutability1
Physics 5
  • Syntax & Types3
  • Classes & Interfaces1
  • Devices1
Creator Economy 3
  • Modules & Structure1
  • Other1
  • Publishing & Islands1
Discovery 3
  • Devices1
  • NPCs & AI1
  • Structs & Enums1
Editor & Project 3
  • Other1
  • Props & Meshes1
  • Tutorials1

Santa's Toy Factory Tycoon Game Loop

Santa's Toy Factory — Tycoon Core (game loop)

The end-to-end tycoon loop from Epic's Santa's Toy Factory: the game_manager that selects the factory owner and wires up the devices, the factory_manager that initialises and runs the factory (including OFFLINE production catch-up while the player was away), the config data tables that drive upgrades and orders, and the persistable player save. Read together to see how generators, upgrade tiers, currency, orders, and persistence connect.

A multi-file module from the Santa's Toy Factory Epic starter project. Epic Games reference Verse, shown for study — review the whole module together (it compiles as part of the full UEFN project, not standalone).

game_manager.verse

Module — 5 files
These files compile together (same module folder).
game_manager.verse
# This file defines the game_manager that handles setting up the game, manages adding and removing players,
# and loads the factory owned by the selected player.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { Factory }
using { UserInterface }
using { Utility }



game_manager := class(creative_device):

    # UI manager for showing game UI.
    @editable
    UIManager:ui_manager = ui_manager{}

    # Billboard showing the owner of the factory.
    @editable
    FactoryBillboard:billboard_device = billboard_device{}

    # Popup dialog telling players that the factory is reset when the factory owner leaves.
    @editable
    FactoryResetPopup:popup_dialog_device = popup_dialog_device{} 

    # The factory manager used to run the factory.
    @editable
    FactoryManager:factory_manager = factory_manager{}

    # Order that the factory can deliver to.
    @editable
    Order:order = order{}

    # Candy CandyCane fields that can be harvested.
    @editable
    CandyCaneFields:[]candy_cane_field = array{}

    # Tutorial guiding the player through the game.
    @editable
    Tutorial:tutorial = tutorial{}

    # Jukebox playing awesome music.
    @editable
    Jukebox:jukebox = jukebox{}

    # Test used when debugging and testing the game.
    @editable
    Test:test = test{}

    # Daily gift given to the player.
    @editable
    DailyGift:daily_gift = daily_gift{}

    # Prop indicating that the factory can be loaded.
    @editable
    LoadFactoryButtonProp:creative_prop = creative_prop{}

    # The trigger that initiates factory loading.
    @editable
    LoadFactoryTrigger:trigger_device = trigger_device{}

    # The selected player that owns the factory and all other game elements. Can be false if nobody owns them yet.
    var MaybeSelectedPlayer:?player = false

    # Runs when the device is started in a running game.
    OnBegin<override>()<suspends>:void=
        # Subscribe callback when factory loading trigger is triggered.
        LoadFactoryTrigger.TriggeredEvent.Subscribe(OnLoadFactoryTriggered)

        # Subscribe callback when players are added or removed.
        Playspace := GetPlayspace()
        Playspace.PlayerAddedEvent().Subscribe(OnPlayerAdded)
        Playspace.PlayerRemovedEvent().Subscribe(OnPlayerRemoved)

        # Make sure that initial players are registered.
        for (Player : Playspace.GetPlayers()):
            OnPlayerAdded(Player)

        # Load factory automatically if there is only one player.
        if:
            Players := Playspace.GetPlayers()
            Players.Length = 1
            Player := Players[0]
        then:
            LoadFactory(Player)

    # Sets the selected player. This will propagate the selected player to all game elements.
    SelectPlayer(MaybePlayer:?player):void=
        set MaybeSelectedPlayer = MaybePlayer

        # Propagate selected player to all game elements. 
        Order.SelectPlayer(MaybePlayer)
        for(CandyCaneField : CandyCaneFields):
            CandyCaneField.SelectPlayer(MaybePlayer)
        Jukebox.SelectPlayer(MaybePlayer)

        # It is important that tutorial sets the selected player before the factory manager
        # since the factory manager does offline production when player is selected.
        Tutorial.SelectPlayer(MaybePlayer)

        # Setting the selected player on the factory manager will initialise the factory for that player. 
        FactoryManager.SelectPlayer(MaybePlayer)

        Test.SelectPlayer(MaybePlayer)

        DailyGift.SelectPlayer(MaybePlayer)

    # When the factory loading trigger is triggered, show the UI for loading a factory.
    OnLoadFactoryTriggered(MaybeAgent:?agent):void=
        if:
            Agent := MaybeAgent?
            Player := player[Agent]
        then:
            LoadFactoryTrigger.Disable()
            UIManager.ShowLoadFactoryUI(Player, Self)

    # Load the factory owned by the player.
    LoadFactory<public>(Player:player):void=
        # Update the billboard with the factory owner.
        FactoryBillboard.SetText(PlayerText("", Player, "'s\nToy Factory"))

        # Register the player as the selected player. This will trigger
        SelectPlayer(option{Player})

        # Show factory and resource UI to all players.
        UIManager.ShowAllFactoryUIs(FactoryManager)
        UIManager.ShowAllResourceUIs(Player)

        # Hide button and disable trigger for loading factory.
        LoadFactoryButtonProp.Hide()
        LoadFactoryTrigger.Disable()

    # Reenables the factory loading trigger. 
    UIClosed<public>():void=
        LoadFactoryTrigger.Enable()

    # When player is added, propagate the information to relevant game elements and update UI.
    OnPlayerAdded(Player:player):void=
        UIManager.PlayerAdded(Player)
        Tutorial.PlayerAdded(Player)

        # Show factory and resource UI to player if factory is loaded.
        if:
            SelectedPlayer := MaybeSelectedPlayer?
        then:
            UIManager.ShowFactoryUI(Player, FactoryManager)
            UIManager.ShowResourceUI(Player, SelectedPlayer)

    # When player is removed, check if that player owned the factory.
    # If so, reset the game state.
    OnPlayerRemoved(Player:player):void=
        UIManager.PlayerRemoved(Player)
        if:
            SelectedPlayer := MaybeSelectedPlayer?
            SelectedPlayer = Player
        then:
            # Reset the billbord showing the factory owner.
            FactoryBillboard.SetText(PlainText("Vacant Toy\nFactory Lot"))

            # Reset the selected player. This will unload the current factory.
            SelectPlayer(false)

            # Close all UIs for all players.
            UIManager.CloseAllGameUIs()

            # Show popup explaining that selected player left.
            FactoryResetPopup.Show()

            # Show button and enable trigger for loading factory.
            LoadFactoryButtonProp.Show()
            LoadFactoryTrigger.Enable()
file_2.verse
# This file defines the factory_manager that handles initialising and running a factory.
# It is also able to calculate how much was produced while the player was offline.
# Finally, it can calculate when the factory storage will be full.

using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Simulation }
using { Config }
using { Persistence }
using { Verse.Scripts.TimeSystem }
using { UserInterface }
using { Utility }

# The minimum distance a product must move on the belt before the mould can produce again.
FactoryBeltMinDistanceToProduce<public>:float = 4.0

factory_manager<public> := class(creative_device):

    # The belts of the factory.
    @editable
    FactoryBelts:[]factory_belt = array{}

    # The moulds of the factory.
    @editable
    FactoryMoulds:[]factory_mould = array{}

    # The wrappers of the factory.
    @editable
    FactoryWrappers:[]factory_wrapper = array{}

    # The storage of the factory.
    @editable
    FactoryStorage:factory_storage = factory_storage{}

    # UI manager for showing offline production UI.
    @editable
    UIManager:ui_manager = ui_manager{}

    # The real time device used to calculate offline production, calculate when the storage is full and
    # run the online production.
    @editable
    RealTime:Verse.Scripts.TimeSystem.real_time = Verse.Scripts.TimeSystem.real_time{}

    # Event signalled when offline production has been calculated.
    # An array containing the number of products produced for each factory line is sent.
    OfflineProductionEvent<public>:event([]int) = event([]int){}

    # The vertical offset from the mould's position to where the belt is located.
    FactoryBeltOffset:vector3 = vector3{X := 0.0, Y := 0.0, Z := 68.0}

    # The selected player that owns the factory. Can be false if nobody owns it yet.
    # The owner will pay for all upgrades and store the state of the factory.
    var MaybeSelectedPlayer:?player = false

    # The factory products that are currently moving down the factory belts.
    var FactoryProductsOnBelts:[]factory_product = array{}

    # Runs when the device is started in a running game.
    OnBegin<override>()<suspends>:void=
        # Hide factory initially since no factory has been loaded yet.
        Update()

    # Sets the selected player. This will initialise the factory and start running it.
    SelectPlayer<public>(MaybePlayer:?player):void=
        set MaybeSelectedPlayer = MaybePlayer

        # Propagate selected player to all factory elements.
        for(FactoryBelt : FactoryBelts):
            FactoryBelt.SelectPlayer(MaybePlayer)

        for(FactoryMould : FactoryMoulds):
            FactoryMould.SelectPlayer(MaybePlayer)

        for(FactoryWrapper : FactoryWrappers):
            FactoryWrapper.SelectPlayer(MaybePlayer)

        FactoryStorage.SelectPlayer(MaybePlayer)

        # Setup correct state of factory.
        Update()

        if:
            SelectedPlayer := MaybeSelectedPlayer?
        then:
            # Calculate offline production.
            ProductsProducedOffline := CalculateOfflineProduction()
            UIManager.ShowOfflineProductionUI(SelectedPlayer, Self, ProductsProducedOffline)

            # Initialise online production.
            InitialiseProduction()

            # Run online production.
            for(Index := 0..FactoryBelts.Length-1):
                if:
                    FactoryBelt := FactoryBelts[Index]
                    FactoryMould := FactoryMoulds[Index]
                    FactoryWrapper := FactoryWrappers[Index]
                then:
                    spawn:
                        RunProduction(FactoryBelt, FactoryMould, FactoryWrapper)

    # Gets the selected player.
    GetSelectedPlayer<public>()<decides><transacts>:player=
        MaybeSelectedPlayer?

    # Updates all factory elements to reflect the current state.
    Update<public>():void=
        for(FactoryBelt : FactoryBelts):
            FactoryBelt.Update()

        for(FactoryMould : FactoryMoulds):
            FactoryMould.Update()

        for(FactoryWrapper : FactoryWrappers):
            FactoryWrapper.Update()

        FactoryStorage.Update()

    # Calculates how many products were produced while offline.
    # Any products on the belt when going offline are not counted by this calculation.
    CalculateOfflineProduction<public>():[]int=
        # Stores how many products were produced during offline production.
        var ProductsProducedOffline:[]int = array{}

        if:
            SelectedPlayer := MaybeSelectedPlayer?
            RemainingStorageCapacity := GetFactoryStorageCapacity[SelectedPlayer] - GetAllFactoryProducts[SelectedPlayer]
        then:
            Now := RealTime.GetDateAndTime()
            NowTotalSeconds := Now.GetTotalSeconds()

            # The calculation is complicated by the fact that all factory lines share the same storage.
            # To find the correct result, first determine when the storage will become full.
            # Solve the following equation, where X is the time when the storage is full, S is the storage capacity,
            # A is the time (seconds) per product of the first mould, O is the latest production time (seconds) of the first mould plus delivery time (seconds):
            # (X-O)/A = S
            # For four moulds the equation becomes:
            # (X-O)/A + (X-P)/B + (X-Q)/C + (X-R)/D = S,
            # where A, B, C and D are times per product and O, P, Q and R are latest production times plus delivery times.
            # The general solution for four moulds is given by:
            # X = (ABCDS + ABCR + ABDQ + ACDP + BCDO) / (ABC + ABD + ACD + BCD)
            # The following code constructs these terms in the numerator and denominator while taking into account that some of the moulds are not active.
            var Numerator:float = 1.0 * RemainingStorageCapacity
            var Denominator:float = 0.0
            var NextTermToAdd:float = 1.0
            for (FactoryMould : FactoryMoulds):
                set ProductsProducedOffline += array{0}

                if:
                    IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                    LatestProductionTimeTotalSeconds := GetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number].GetTotalSeconds()
                    DeliveryTime := GetFactoryBeltDeliveryTime[FactoryMould.Number, SelectedPlayer]
                    ProductionTime := GetFactoryMouldProductionTime[FactoryMould.Number, SelectedPlayer]
                    MinDistanceTime := FactoryBeltMinDistanceToProduce / GetFactoryBeltSpeed[FactoryMould.Number, SelectedPlayer]
                    ActualProductionTime := Max(ProductionTime, MinDistanceTime)
                then:
                    set Numerator *= ActualProductionTime
                    set Denominator *= ActualProductionTime
                    set Numerator += (LatestProductionTimeTotalSeconds + DeliveryTime) * NextTermToAdd
                    set Denominator += NextTermToAdd
                    set NextTermToAdd *= ActualProductionTime

            TimeWhenFull := Numerator / Denominator

            # If the time is well defined, calculate how much each mould has contributed.
            if (TimeWhenFull.IsFinite[]):
                for (FactoryMould : FactoryMoulds):
                    if:
                        IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                        LatestProductionTimeTotalSeconds := GetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number].GetTotalSeconds()
                        DeliveryTime := GetFactoryBeltDeliveryTime[FactoryMould.Number, SelectedPlayer]
                        ProductionTime := GetFactoryMouldProductionTime[FactoryMould.Number, SelectedPlayer]
                        MinDistanceTime := FactoryBeltMinDistanceToProduce / GetFactoryBeltSpeed[FactoryMould.Number, SelectedPlayer]
                        ActualProductionTime := Max(ProductionTime, MinDistanceTime)
                    then:
                        # Determine the total time this mould has to produce and store products.
                        TimeToProduce := Max(0.0, Min(NowTotalSeconds, TimeWhenFull) - (LatestProductionTimeTotalSeconds + DeliveryTime))
                        if:
                            # If the time when the storage becomes full is in the future, ceil the amount of products to ensure that all products are counted as stored.
                            # If the time is in the past, round the amount of products to ensure that the storage is not overflowed.
                            Products := if (NowTotalSeconds > TimeWhenFull) then Round[TimeToProduce / ActualProductionTime] else Ceil[TimeToProduce / ActualProductionTime]
                            set ProductsProducedOffline[FactoryMould.Number] = Products
                            StoreFactoryProduct[SelectedPlayer, FactoryMould.Number, Products]
                            SetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number, Verse.Scripts.TimeSystem.MakeDateAndTime(LatestProductionTimeTotalSeconds + Products * ActualProductionTime)]

            # Signal that offline production was performed.
            OfflineProductionEvent.Signal(ProductsProducedOffline)

        return ProductsProducedOffline

    # Calculates the date and time at which the storage will be full.
    # This shares similarities with CalculateOfflineProduction but does not actually store anything during the calculation.
    # Also, it takes products already moving down the belts into consideration.
    CalculateDateAndTimeForFullStorage<public>():Verse.Scripts.TimeSystem.date_and_time=
        Now := RealTime.GetDateAndTime()
        NowTotalSeconds := Now.GetTotalSeconds()

        # Since the factory might not have any active moulds, the storage might never be filled. Therefore start out with the result being infinite. 
        var ResultTotalSeconds:float = Inf

        if:
            SelectedPlayer := MaybeSelectedPlayer?
            AllFactoryProducts := GetAllFactoryProducts[SelectedPlayer]
            Capacity := GetFactoryStorageCapacity[SelectedPlayer]
        then:
            # Keeps track of how much storage space is left.
            var StorageSpaceLeft : int = Capacity - AllFactoryProducts 
            # The calculation is complicated by the fact that all factory lines share the same storage.
            # First, continually calculate the next time each product on the belts will reach the storage.
            # During this time, new products produced could overtake other products due to different belt speeds,
            # so take these into account in the calculation as well.
            var NextStoreTimesTotalSeconds:[]float = array{}
            for(FactoryMould : FactoryMoulds):
                if:
                    IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                    # If just one mould is active, register a valid, finite value. 
                    set ResultTotalSeconds = NowTotalSeconds
                    LatestProductionTime := GetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number]
                    LatestProductionTimeTotalSeconds := if (LatestProductionTime.IsValid[]) then LatestProductionTime.GetTotalSeconds() else NowTotalSeconds
                    ProductionTime := GetFactoryMouldProductionTime[FactoryMould.Number, SelectedPlayer]
                    DeliveryTime := GetFactoryBeltDeliveryTime[FactoryMould.Number, SelectedPlayer]
                    StartTime := NowTotalSeconds + Max(0.0, ProductionTime - (NowTotalSeconds - LatestProductionTimeTotalSeconds))
                then:
                    set NextStoreTimesTotalSeconds = NextStoreTimesTotalSeconds + array{StartTime + DeliveryTime}
                else:
                    set NextStoreTimesTotalSeconds = NextStoreTimesTotalSeconds + array{Inf}

            # Any products already on the belt due to production initialisation or running production should also contribute storage times.
            for(FactoryProduct : FactoryProductsOnBelts):
                if:
                    FactoryBelt := FactoryBelts[FactoryProduct.Number]
                    IsFactoryMouldActive[SelectedPlayer, FactoryProduct.Number]
                    FactoryBeltSpeed := GetFactoryBeltSpeed[FactoryProduct.Number, SelectedPlayer]
                    DeliveryTimeLeft := FactoryProduct.DistanceLeft / FactoryBeltSpeed
                then:
                    set NextStoreTimesTotalSeconds = NextStoreTimesTotalSeconds + array{NowTotalSeconds + DeliveryTimeLeft}

            # Calculate and cache the production time for each mould.
            var ActualProductionTimes:[]float = array{}
            for(Index := 0..FactoryMoulds.Length-1):
                if:
                    ProductionTime := GetFactoryMouldProductionTime[Index, SelectedPlayer]
                    MinDistanceTime := FactoryBeltMinDistanceToProduce / GetFactoryBeltSpeed[Index, SelectedPlayer]
                    ActualProductionTime := Max(ProductionTime, MinDistanceTime)
                then:
                    set ActualProductionTimes += array{ActualProductionTime}

            # While there are products on the belt and room left in the storage, find the next, valid storage time and store it as the intermediate result of the calculation.
            loop:
                if (NextStoreTimesTotalSeconds.Length = FactoryMoulds.Length):
                    break

                var NextStoreTimeTotalSeconds:float = Inf
                var NextStoreIndex:int = -1
                for(Index := 0..NextStoreTimesTotalSeconds.Length-1):
                    if:
                        ThisStoreTimeTotalSeconds := NextStoreTimesTotalSeconds[Index]
                        ThisStoreTimeTotalSeconds < NextStoreTimeTotalSeconds
                        set NextStoreTimeTotalSeconds = ThisStoreTimeTotalSeconds
                        set NextStoreIndex = Index

                if:
                    StorageSpaceLeft > 0
                    NextStoreTimeTotalSeconds.IsFinite[]
                    set StorageSpaceLeft -= 1
                    set ResultTotalSeconds = NextStoreTimeTotalSeconds
                    # If this storage comes straight from a mould, update it to the next storage time for this factory line.
                    if (NextStoreIndex < FactoryMoulds.Length):
                        set NextStoreTimesTotalSeconds[NextStoreIndex] += ActualProductionTimes[NextStoreIndex]
                    # Otherwise, the storage comes from a product that was on the belt already, so remove the storage time.
                    else:
                        NewNextStoreTimesTotalSeconds := NextStoreTimesTotalSeconds.RemoveElement[NextStoreIndex]
                        set NextStoreTimesTotalSeconds = NewNextStoreTimesTotalSeconds
                else:
                    break

            # If there is still storage space left, compute when the storage will become full from mould production alone.
            # See CalculateOfflineProduction for an explanation of the equation that is solved.
            if (StorageSpaceLeft > 0):
                var Numerator:float = 1.0 * (Capacity - FactoryProductsOnBelts.Length)
                var Denominator:float = 0.0
                var NextTermToAdd:float = 1.0
                for (FactoryMould : FactoryMoulds):

                    if:
                        IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                        LatestProductionTime := GetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number]
                        LatestProductionTimeTotalSeconds := if (LatestProductionTime.IsValid[]) then LatestProductionTime.GetTotalSeconds() else NowTotalSeconds
                        DeliveryTime := GetFactoryBeltDeliveryTime[FactoryMould.Number, SelectedPlayer]
                        ProductionTime := GetFactoryMouldProductionTime[FactoryMould.Number, SelectedPlayer]
                        MinDistanceTime := FactoryBeltMinDistanceToProduce / GetFactoryBeltSpeed[FactoryMould.Number, SelectedPlayer]
                        ActualProductionTime := Max(ProductionTime, MinDistanceTime)
                    then:
                        set Numerator *= ActualProductionTime
                        set Denominator *= ActualProductionTime
                        set Numerator += (LatestProductionTimeTotalSeconds + DeliveryTime) * NextTermToAdd
                        set Denominator += NextTermToAdd
                        set NextTermToAdd *= ActualProductionTime

                # This result is not entirely accurate since different products that are halfway down the belt can sum to one product in the storage
                # making the storage full even though none of the products have actually been delivered.
                set ResultTotalSeconds = Numerator / Denominator

        # If the result is a valid, finite value, convert to a valid Verse.Scripts.TimeSystem.date_and_time.
        return Verse.Scripts.TimeSystem.MakeDateAndTime(ResultTotalSeconds)


    # Shows the factory belts as initially blocked and full of products if the storage is full when initialising the factory.
    InitialiseProduction():void=
        if:
            SelectedPlayer := MaybeSelectedPlayer?
            not IsRoomInFactoryStorage[SelectedPlayer]
        then:
            for(Index := 0..FactoryBelts.Length-1):
                if:
                    FactoryBelt := FactoryBelts[Index]
                    FactoryMould := FactoryMoulds[Index]
                    FactoryWrapper := FactoryWrappers[Index]
                    IsFactoryBeltBuilt[SelectedPlayer, FactoryBelt.Number]
                    IsFactoryMouldBuilt[SelectedPlayer, FactoryMould.Number]
                    IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                then:
                    # Block the belt.
                    FactoryBelt.SetBlocked(true)
                    FactoryBelt.Update()

                    # Fill the belt with products.
                    var CurrentBeltDistance:float = 0.0
                    if:
                        FactoryBeltSpeed := GetFactoryBeltSpeed[FactoryBelt.Number, SelectedPlayer]
                        FactoryMouldProductionTime := GetFactoryMouldProductionTime[FactoryBelt.Number, SelectedPlayer]
                    then:
                        # Calculate how much the belt moves between productions. This is used to determine the distance between products on the belt.
                        FactoryBeltMovementBetweenProduction := Max(FactoryBeltMinDistanceToProduce, FactoryMouldProductionTime * FactoryBeltSpeed)
                        # Starting from the end of the belt, while there is room left on the belt, place a product with the determined distance to the previous.
                        loop:
                            spawn:
                                Produce(FactoryBelt, FactoryMould, FactoryWrapper, CurrentBeltDistance, ?PlayMouldAndWrapperAnimation := false)
                            set CurrentBeltDistance += FactoryBeltMovementBetweenProduction
                            if (CurrentBeltDistance > FactoryBeltTransportDistance):
                                break

    # Runs the production for a factory line consisting of belt, mould and wrapper.
    RunProduction(FactoryBelt:factory_belt, FactoryMould:factory_mould, FactoryWrapper:factory_wrapper)<suspends>:void=
        if:
            SelectedPlayer := MaybeSelectedPlayer?
            var PreviousElapsedTime:float = GetSimulationElapsedTime()
            var TimePassed:float = 0.0
            var DistanceSinceLatestProduction:float = Inf
            # Determine how much time has passed since the last production of the mould.
            LatestProductionTime := GetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number]
            if:
                LatestProductionTime.IsValid[]
            then:
                StartTime := RealTime.GetDateAndTime()
                SecondsSinceLatestProduction := StartTime.Subtract(LatestProductionTime).GetTotalSeconds()
                set TimePassed = SecondsSinceLatestProduction
            else:
                if:
                    FactoryMouldProductionTime := GetFactoryMouldProductionTime[FactoryMould.Number, SelectedPlayer]
                    set TimePassed = FactoryMouldProductionTime
        then:
            loop:
                # If the selected player is removed, stop the production.
                if (not MaybeSelectedPlayer?):
                    break

                # Determine how much time has passed since last iteration of the loop.
                CurrentElapsedTime := GetSimulationElapsedTime()
                DeltaTime := CurrentElapsedTime - PreviousElapsedTime
                set PreviousElapsedTime = CurrentElapsedTime
                # Update the time passed.
                set TimePassed += DeltaTime
                # If the belt is not blocked and the mould is active, the belt will move.
                # Update the distance that it has moved.
                if:
                    not FactoryBelt.IsBlocked[]
                    IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                    FactoryBeltSpeed := GetFactoryBeltSpeed[FactoryBelt.Number, SelectedPlayer]
                then:
                    set DistanceSinceLatestProduction += FactoryBeltSpeed * DeltaTime
                # If the belt is not blocked and the mould is active, determine if enough time has passed
                # and if the belt has moved far enough for the mould to be vacant from the previous production.
                # If so, produce a new product.
                if:
                    not FactoryBelt.IsBlocked[]
                    IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                    FactoryMouldProductionTime := GetFactoryMouldProductionTime[FactoryMould.Number, SelectedPlayer]
                    TimePassed >= FactoryMouldProductionTime and DistanceSinceLatestProduction >= FactoryBeltMinDistanceToProduce
                    set TimePassed = 0.0
                    set DistanceSinceLatestProduction = 0.0
                    SetFactoryMouldLatestProductionTime[SelectedPlayer, FactoryMould.Number, RealTime.GetDateAndTime()]
                then:
                    spawn:
                        Produce(FactoryBelt, FactoryMould, FactoryWrapper, FactoryBeltTransportDistance)
                Sleep(0.0)

    # Produces a product and tracks it all the way down the factory line through a wrapper until it reaches the storage.
    # There is an option to not play mould and wrapper animations in case the production is part of the initialisation.
    Produce(FactoryBelt:factory_belt, FactoryMould:factory_mould, FactoryWrapper:factory_wrapper, InitialDistance:float, ?PlayMouldAndWrapperAnimation:logic = true)<suspends>:void=
        if (PlayMouldAndWrapperAnimation?):
            FactoryMould.Produce()

        # Determine where to spawn the product prop and where it should end up.
        FactoryMouldTransform := FactoryMould.PositionProp.GetTransform()
        BeltStartPosition := FactoryMouldTransform.Translation + FactoryBeltOffset
        BeltEndPosition := FactoryBelt.EndProp.GetTransform().Translation + FactoryBeltOffset

        # Determine where along the journey the wrapper is placed.
        FactoryWrapperTransform := FactoryWrapper.PositionProp.GetTransform()
        WrapperPosition := FactoryWrapperTransform.Translation + FactoryBeltOffset
        DistanceToWrapper := (WrapperPosition - BeltEndPosition).Length()
        var WrapperTriggered:logic = false

        if:
            Direction := (BeltStartPosition - BeltEndPosition).MakeUnitVector[]
            InitialPosition := BeltEndPosition + InitialDistance * LEGOUnitsToFortniteUnits * Direction
        then:
            # Spawn the product prop.
            SpawnResult := SpawnProp(FactoryMould.Product, InitialPosition, IdentityRotation())
            if:
                var Prop:creative_prop = SpawnResult(0)?
            then:
                # Keep track of products currently on the belts.
                FactoryProduct := factory_product{Number := FactoryMould.Number, DistanceLeft := InitialDistance}
                set FactoryProductsOnBelts += array{FactoryProduct}

                var PreviousElapsedTime:float = GetSimulationElapsedTime()
                loop:
                    if:
                        SelectedPlayer := MaybeSelectedPlayer?
                        Prop.IsValid[]
                    then:
                        # Determine how much time has passed since last iteration of the loop.
                        CurrentElapsedTime := GetSimulationElapsedTime()
                        DeltaTime := CurrentElapsedTime - PreviousElapsedTime
                        set PreviousElapsedTime = CurrentElapsedTime
                        # Update the position at which the product should be now depending on if the belt is blocked and the mould is active.
                        if:
                            not FactoryBelt.IsBlocked[]
                            IsFactoryMouldActive[SelectedPlayer, FactoryMould.Number]
                            FactoryBeltSpeed := GetFactoryBeltSpeed[FactoryBelt.Number, SelectedPlayer]
                        then:
                            set FactoryProduct.DistanceLeft -= FactoryBeltSpeed * DeltaTime
                        CurrentPosition := BeltEndPosition + FactoryProduct.DistanceLeft * LEGOUnitsToFortniteUnits * Direction
                        # Check if the product has passed the wrapper. If it has, replace the prop with the wrapper's prop.
                        if:
                            IsFactoryWrapperBuilt[SelectedPlayer, FactoryWrapper.Number]
                            not WrapperTriggered?
                            FactoryProduct.DistanceLeft * LEGOUnitsToFortniteUnits < DistanceToWrapper
                        then:
                            if (PlayMouldAndWrapperAnimation?):
                                FactoryWrapper.Process(SelectedPlayer)
                            set WrapperTriggered = true
                            OldProp := Prop
                            NewSpawnResult := SpawnProp(FactoryWrapper.Product, Prop.GetTransform())
                            if:
                                NewProp := NewSpawnResult(0)?
                                set Prop = NewProp
                            then:
                                # Hide the prop before disposing it to avoid showing LEGO explosion effect.
                                OldProp.Hide()
                                OldProp.Dispose()

                        # Move the product prop a little.
                        Prop.MoveTo(CurrentPosition, IdentityRotation(), 0.1)
                        # When the product has reached the storage, check if there is any room left.
                        if (FactoryProduct.DistanceLeft <= 0.0):
                            # If there is, store the product, unblock the belt and return. The storage will continue handling the product prop.
                            # If there is no room, block the factory belt, and try again later.
                            if:
                                IsRoomInFactoryStorage[SelectedPlayer]
                                StoreFactoryProduct[SelectedPlayer, FactoryMould.Number, 1]
                                NewFactoryProductsOnBelts := FactoryProductsOnBelts.RemoveAll[FactoryProduct, CompareFactoryProducts]
                                set FactoryProductsOnBelts = NewFactoryProductsOnBelts
                            then:
                                FactoryStorage.Store(FactoryMould.Number, Prop)
                                if (FactoryBelt.IsBlocked[]):
                                    FactoryBelt.SetBlocked(false)
                                    FactoryBelt.Update()
                            else:
                                if (not FactoryBelt.IsBlocked[]):
                                    FactoryBelt.SetBlocked(true)
                                    FactoryBelt.Update()
                            if:
                                not FactoryBelt.IsBlocked[]
                            then:
                                return
                    else:
                        # If the selected player is removed or the prop became invalid, dispose of the product and return.
                        if:
                            NewFactoryProductsOnBelts := FactoryProductsOnBelts.RemoveAll[FactoryProduct, CompareFactoryProducts]
                            set FactoryProductsOnBelts = NewFactoryProductsOnBelts
                        # Hide the prop before disposing it to avoid showing LEGO explosion effect.
                        Prop.Hide()
                        Prop.Dispose()
                        return
file_3.verse
# This file contains the data used to configure the factory in the game.
# This includes upgrade prices, max levels, production speeds, storage capacities and more.
# It also contains functions for getting all this data. 

using { /Verse.org/Simulation }
using { Persistence }

# Blubberchops: "Looks like this is the place to modify the upgrade prices for belts."
#               "Let's make them cheaper!"

# Defines the upgrade price per level for each factory belt.
FactoryBeltPrices: [][]int =
    # Each column is a belt.       0             1               2               3
    # Each row is a level.
    array{  array{                 1,           5,            10,          15},
            array{                5,          10,           25,          35},
            array{               10,          25,           50,         100},
            array{               20,          50,          100,        200},
            array{               30,         100,          200,        300},
            array{               40,         200,          400,        500},
            array{               50,         250,          500,        750},
            array{               75,         375,          750,       1000},
            array{              100,         500,         1000,       1500},
            array{              150,         750,         1500,       2000},
            array{              200,        1000,         2000,       3000},
            array{              250,        1250,         2500,       3500},
            array{              300,        1500,         3000,       4000},
            array{              400,        2000,         4000,       5000},
            array{              500,        2500,         5000,       7500}}

# Defines the upgrade price per level for each factory mould.
FactoryMouldPrices : [][]int = 
    # Each column is a mould.      0             1               2               3
    # Each row is a level.
    array{  array{                 5,           25,            500,          5000},
            array{                15,           75,           1500,         15000},
            array{                25,          125,           2500,         25000},
            array{                50,          250,           5000,         50000},
            array{               100,          500,          10000,        100000},
            array{               150,          750,          15000,        150000},
            array{               200,         1000,          20000,        200000},
            array{               250,         1250,          25000,        250000},
            array{               300,         1500,          30000,        300000},
            array{               350,         1750,          35000,        350000},
            array{               400,         2000,          40000,        400000},
            array{               450,         2250,          45000,        450000},
            array{               500,         2500,          50000,        500000},
            array{               600,         3000,          60000,        600000},
            array{               700,         3500,          70000,        700000},
            array{               800,         4000,          80000,        800000},
            array{               900,         4500,          90000,        900000},
            array{              1000,         5000,         100000,       1000000},
            array{              1250,         6250,         125000,       1250000},
            array{              1500,         7500,         150000,       1500000},
            array{              1750,         8750,         175000,       1750000},
            array{              2000,        10000,         200000,       2000000},
            array{              2250,        11250,         225000,       2250000},
            array{              2500,        12500,         250000,       2500000},
            array{              2750,        13750,         275000,       2750000},
            array{              3000,        15000,         300000,       3000000},
            array{              3500,        17500,         350000,       3500000},
            array{              4000,        20000,         400000,       4000000},
            array{              4500,        22500,         450000,       4500000},
            array{              5000,        25000,         500000,       5000000}}

# Defines the upgrade price per level for each factory wrapper.
FactoryWrapperPrices : [][]int = 
    # Each column is a wrapper.    0             1               2               3
    # Each row is a level.
    array{  array{                25,          100,           2000,         20000},
            array{                50,          250,           5000,         50000},
            array{               100,          500,          10000,        100000},
            array{               500,         2500,          50000,        250000},
            array{              1000,         5000,         100000,        500000},
            array{              2500,        12500,         250000,       1000000},
            array{              5000,        25000,         500000,       2500000},
            array{             10000,        50000,        1000000,       5000000},
            array{             15000,        75000,        1500000,       7500000},
            array{             20000,       100000,        2000000,       9999999}}

# Defines the upgrade price per level for the factory storage.
FactoryStoragePrices: []int =
    # Each column is a level.
    array{ 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000, 250000, 500000}

# Defines the length of factory belts in LEGO® units (knobs).
# If the length of the belts is changed, this value should be updated.
FactoryBeltTransportDistance<public> : float = 60.0

# Defines the base amount of products produced per minute for each factory mould.
FactoryMouldBaseProductsPerMinute : []float = 
    # Each column is a mould.
    array{ 2.0, 1.0, 0.5, 0.25}

# Gets the max level for the factory belt.
GetFactoryBeltMaxLevel<public>(FactoryBeltNumber:int)<decides><transacts>:int=
    FactoryBeltPrices.Length

# Gets the max level for the factory mould.
GetFactoryMouldMaxLevel<public>(FactoryMouldNumber:int)<decides><transacts>:int=
    FactoryMouldPrices.Length

# Gets the max level for the factory wrapper.
GetFactoryWrapperMaxLevel<public>(FactoryWrapperNumber:int)<decides><transacts>:int=
    FactoryWrapperPrices.Length

# Gets the max level for the factory storage.
GetFactoryStorageMaxLevel<public>()<decides><transacts>:int=
    FactoryStoragePrices.Length

# Gets the upgrade price of the factory belt for the player.
GetFactoryBeltUpgradePrice<public>(FactoryBeltNumber:int, Player:player)<decides><transacts>:int=
    FactoryBeltLevel := GetFactoryBeltLevel[Player, FactoryBeltNumber]
    FactoryBeltIndex := Min(FactoryBeltLevel, FactoryBeltPrices.Length-1)
    FactoryBeltPrices[FactoryBeltIndex][FactoryBeltNumber]

# Gets the upgrade price of the factory mould for the player.
GetFactoryMouldUpgradePrice<public>(FactoryMouldNumber:int, Player:player)<decides><transacts>:int=
    FactoryMouldLevel := GetFactoryMouldLevel[Player, FactoryMouldNumber]
    FactoryMouldIndex := Min(FactoryMouldLevel, FactoryMouldPrices.Length-1)
    FactoryMouldPrices[FactoryMouldIndex][FactoryMouldNumber]

# Gets the upgrade price of the factory wrapper for the player.
GetFactoryWrapperUpgradePrice<public>(FactoryWrapperNumber:int, Player:player)<decides><transacts>:int=
    FactoryWrapperLevel := GetFactoryWrapperLevel[Player, FactoryWrapperNumber]
    FactoryWrapperIndex := Min(FactoryWrapperLevel, FactoryWrapperPrices.Length-1)
    FactoryWrapperPrices[FactoryWrapperIndex][FactoryWrapperNumber]

# Gets the upgrade price of the factory storage for the player.
GetFactoryStorageUpgradePrice<public>(Player:player)<decides><transacts>:int=
    FactoryStorageLevel := GetFactoryStorageLevel[Player]
    FactoryStorageIndex := Min(FactoryStorageLevel, FactoryStoragePrices.Length-1)
    FactoryStoragePrices[FactoryStorageIndex]

# Gets the speed of the factory belt for the player.
GetFactoryBeltSpeed<public>(FactoryBeltNumber:int, Player:player)<decides><transacts>:float=
    FactoryBeltLevel := GetFactoryBeltLevel[Player, FactoryBeltNumber]
    # This formula slowly makes the factory belt faster as the level increases.
    # It starts with a value of 0.5 at level 1 and hits a value of 5.0 at level 15.
    FactoryBeltLevel * 0.25 + 0.25

# Gets the speed of the factory mould for the player.
GetFactoryMouldSpeed<public>(FactoryMouldNumber:int, Player:player)<decides><transacts>:float=
    FactoryMouldLevel := GetFactoryMouldLevel[Player, FactoryMouldNumber]
    # This formula very slowly makes the factory mould faster as the level increases.
    # It starts with a value of 1.0 at level 1 and hits a value of 30.0 at level 30.
    FactoryMouldLevel * 1.0 + 0.0

# Gets the price multiplier of the factory wrapper for the player.
GetFactoryWrapperMultiplier<public>(FactoryWrapperNumber:int, Player:player)<decides><transacts>:float=
    FactoryWrapperLevel := GetFactoryWrapperLevel[Player, FactoryWrapperNumber]
    # This formula slowly makes the factory wrapper multiplier higher as the level increases.
    # It starts with a value of 1.4 at level 1 and hits a value of 5.0 at level 10.
    FactoryWrapperLevel * 0.4 + 1.0

# Gets the time in seconds it takes for a product to make it from the factory mould to the factory storage for the player.
GetFactoryBeltDeliveryTime<public>(FactoryBeltNumber:int, Player:player)<decides><transacts>:float=
    FactoryBeltSpeed := GetFactoryBeltSpeed[FactoryBeltNumber, Player]
    FactoryBeltTransportDistance / FactoryBeltSpeed

# Gets the time in seconds it takes for the factory mould to produce a product for the player.
GetFactoryMouldProductionTime<public>(FactoryMouldNumber:int, Player:player)<decides><transacts>:float=
    FactoryMouldSpeed := GetFactoryMouldSpeed[FactoryMouldNumber, Player]
    FactoryMouldProductsPerMinute := FactoryMouldSpeed * FactoryMouldBaseProductsPerMinute[FactoryMouldNumber]
    60.0 / FactoryMouldProductsPerMinute

# Gets the factory storage capacity for the player.
GetFactoryStorageCapacity<public>(Player:player)<decides><transacts>:int=
    FactoryStorageLevel := GetFactoryStorageLevel[Player]
    # This formula makes the factory storage capactity grow exponentially.
    # It starts with a value of 10.0 at level 1 and doubles for each level.
    Ceil[10.0 * Pow(2.0, FactoryStorageLevel * 1.0 - 1.0)]
file_4.verse
# This file contains the data used to configure how orders are generated in the game.
# It also contains functions for getting and generating the next order. 

using { /Verse.org/Random }
using { /Verse.org/Simulation }
using { Persistence }

# Contains information for one line in an order.
order_line<public> := struct:
    ProductNumber<public>:int
    ProductsOrdered<public>:int
    BasePrice<public>:int

# Defines the first orders a player will get.
# This gives a predictable beginning to the game and ensures that placed orders play well with the tutorial.
# This also ensures that the player becomes accustomed to the game before random orders are generated.
FirstOrders: [][]tuple(int, int, int) =
    # Each row is an order.
    # Each column is an order line (product number, amount ordered, base price per unit).
    array{  array{(0, 1, 5)},
            array{(0, 5, 5)},
            array{(0, 10, 5), (1, 5, 10)},
            array{(0, 20, 10)}}

# Defines the base prices for each factory product.
ProductBasePrices: []int =
    # Each  column is a product.
    array{ 5, 10, 25, 100}

# Defines the scrap prices for each factory product.
ProductScrapPrices: []int =
    # Each column is a product.
    array{ 1, 1, 1, 1}

# Gets the scrap price for a product.
GetProductScrapPrice<public>(ProductNumber:int)<decides><transacts>:int=
    ProductScrapPrices[ProductNumber]

# Gets the next order for the player.
GetOrder<public>(Player:player)<decides><transacts>:[]order_line=
    OrderNumber := GetOrderNumber[Player]
    # If the order number is one of the first orders, create the order from the data.
    # Otherwise, generate the order randomly.
    if:
        OrderNumber < FirstOrders.Length
        OrderLines := FirstOrders[OrderNumber]
    then:
        for(OrderLine : OrderLines):
            order_line{ProductNumber := OrderLine(0), ProductsOrdered := OrderLine(1), BasePrice := OrderLine(2)}
    else:
        GenerateOrder[Player, OrderNumber]

# Generates an order randomly from the products that the factory can currently produce.
GenerateOrder(Player:player, OrderNumber:int)<decides><transacts>:[]order_line=
    # Determine the products available.
    var ProductsAvailable: []int = 
        for(ProductNumber := 0..3, IsFactoryMouldBuilt[Player, ProductNumber]):
            ProductNumber

    # Make a random amount of order lines.
    # Assumes that at least one product is available.
    var OrderLineCount:int = GetRandomInt(1, Min(3, ProductsAvailable.Length))

    var Order:[]order_line = array{}
    for (OrderLine := 1..OrderLineCount):
        # Select a random available product and add to order.
        RandomProductIndex := GetRandomInt(0, ProductsAvailable.Length-1)
        ProductNumber := ProductsAvailable[RandomProductIndex]
        ProductsOrdered := GetProductsOrdered[ProductNumber, OrderNumber]
        BasePrice := GetBasePrice[ProductNumber, OrderNumber]
        set Order = Order + array{order_line{ProductNumber := ProductNumber, ProductsOrdered := ProductsOrdered, BasePrice := BasePrice}}
        # Remove product from available products.
        NewProductsAvailable := ProductsAvailable.RemoveElement[RandomProductIndex]
        set ProductsAvailable = NewProductsAvailable
    Order

# Gets a random amount of products ordered.
GetProductsOrdered(ProductNumber:int, OrderNumber:int)<decides><transacts>:int=
    # This formula ensures that more products will be ordered later in the game.
    # It also ensures that products with a higher number (more expensive to produce) are ordered in a smaller quantity.
    Ceil((OrderNumber + GetRandomInt(0, 5)) * 5 / (ProductNumber + 1))

# Gets a random base price for a product.
GetBasePrice(ProductNumber:int, OrderNumber:int)<decides><transacts>:int=
    # This formula ensures that base prices are higher later in the game.
    ProductBasePrice := ProductBasePrices[ProductNumber]
    Ceil((OrderNumber + GetRandomInt(0, 20)) / 10) * ProductBasePrice
player_info.verse
# This file defines player_info, the collection of persistable information stored for each player.
# It also contains functions for creating, updating and returning information about a player.

using { /Verse.org/Simulation }
using { Config }
using { Verse.Scripts.TimeSystem }

# Maps each player to their persisted information.
var PlayerInfoMap:weak_map(player, player_info) = map{}
#var PlayerInfoMap:weak_map(session, player_info) = map{}

# This tracks all persistable information for a player.
player_info := class<final><persistable>:

    # The version of the current player info.
    Version:int = 0

    # Latest harvest times of all candy CandyCanes.
    CandyCaneHarvestTimes:[]Verse.Scripts.TimeSystem.date_and_time =  array{Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}, Verse.Scripts.TimeSystem.date_and_time{}}

    # The amount of gold the player has.
    Gold:int = 0

    # The state of the factory.
    FactoryBeltInfos:[]factory_belt_info = array{factory_belt_info{}, factory_belt_info{}, factory_belt_info{}, factory_belt_info{}}
    FactoryMouldInfos:[]factory_mould_info = array{factory_mould_info{}, factory_mould_info{}, factory_mould_info{}, factory_mould_info{}}
    FactoryWrapperInfos:[]factory_wrapper_info = array{factory_wrapper_info{}, factory_wrapper_info{}, factory_wrapper_info{}, factory_wrapper_info{}}
    FactoryStorageInfo:factory_storage_info = factory_storage_info{}

    # The current order.
    OrderNumber:int = 0
    OrderInfo:order_info = order_info{}

    # Professor Flopkins: "Hmm, we should store the date and time."
    #                     "Maybe we could use something similar to line 18?"
    GiftOpeningTime:Verse.Scripts.TimeSystem.date_and_time =  Verse.Scripts.TimeSystem.date_and_time{}


# Creates a new player_info with the same values as the previous player_info.
MakePlayerInfo<constructor>(Src:player_info)<transacts> := player_info:
    Version := Src.Version
    CandyCaneHarvestTimes := Src.CandyCaneHarvestTimes
    Gold := Src.Gold
    FactoryBeltInfos := Src.FactoryBeltInfos
    FactoryMouldInfos := Src.FactoryMouldInfos
    FactoryWrapperInfos := Src.FactoryWrapperInfos
    FactoryStorageInfo := Src.FactoryStorageInfo
    OrderNumber := Src.OrderNumber
    OrderInfo := Src.OrderInfo
    # Professor Flopkins: "We need to make sure to copy over the latest gift opening time"
    #                     "It should look completely similar to the other lines in this constructor."
    GiftOpeningTime := Src.GiftOpeningTime

# Creates player information for the player if none exists. 
CheckPlayerInfoForPlayer(Player:player)<decides><transacts>:void=
    Player.IsActive[]
    if(not PlayerInfoMap[Player]):
        set PlayerInfoMap[Player] = player_info{}

# Sets the harvest time of the candy CandyCane for the player. 
SetCandyCaneHarvestTime<public>(Player:player, CandyCaneNumber:int, CandyCaneHarvestTime:Verse.Scripts.TimeSystem.date_and_time)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    var NewCandyCaneHarvestTimes:[]Verse.Scripts.TimeSystem.date_and_time = SourceInfo.CandyCaneHarvestTimes
    set NewCandyCaneHarvestTimes[CandyCaneNumber] = CandyCaneHarvestTime

    set PlayerInfoMap[Player] = player_info:
        CandyCaneHarvestTimes := NewCandyCaneHarvestTimes
        MakePlayerInfo<constructor>(SourceInfo)

# Gets the harvest time of the candy CandyCane for the player. 
GetCandyCaneHarvestTime<public>(Player:player, CandyCaneNumber:int)<decides><transacts>:Verse.Scripts.TimeSystem.date_and_time=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].CandyCaneHarvestTimes[CandyCaneNumber]

# Grant the amount of gold to the player.
GrantGold<public>(Player:player, Amount:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]

    set PlayerInfoMap[Player] = player_info:
        Gold := SourceInfo.Gold + Amount
        MakePlayerInfo<constructor>(SourceInfo)

# Spend the amount of gold for the player.
SpendGold<public>(Player:player, Amount:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]

    # Fails if player does not have enough gold.
    SourceInfo.Gold >= Amount

    set PlayerInfoMap[Player] = player_info:
        Gold := SourceInfo.Gold - Amount
        MakePlayerInfo<constructor>(SourceInfo)

# Gets the amount of gold the player has.
GetGold<public>(Player:player)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].Gold

# Builds the factory belt for the player.
BuildFactoryBelt<public>(Player:player, FactoryBeltNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryBeltInfo := SourceInfo.FactoryBeltInfos[FactoryBeltNumber]

    # Fails if belt is already built.
    SourceFactoryBeltInfo.Built = false

    var NewFactoryBeltInfos:[]factory_belt_info = SourceInfo.FactoryBeltInfos
    set NewFactoryBeltInfos[FactoryBeltNumber] = factory_belt_info:
        Built := true
        MakeFactoryBeltInfo<constructor>(SourceFactoryBeltInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryBeltInfos := NewFactoryBeltInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Succeeds if the factory belt is built for the player.
IsFactoryBeltBuilt<public>(Player:player, FactoryBeltNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryBeltInfos[FactoryBeltNumber].Built?

# Upgrades the level of the factory belt for the player.
UpgradeFactoryBeltLevel<public>(Player:player, FactoryBeltNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    var NewFactoryBeltInfos:[]factory_belt_info = SourceInfo.FactoryBeltInfos
    SourceFactoryBeltInfo := SourceInfo.FactoryBeltInfos[FactoryBeltNumber]
    set NewFactoryBeltInfos[FactoryBeltNumber] = factory_belt_info:
        Level := SourceFactoryBeltInfo.Level + 1
        MakeFactoryBeltInfo<constructor>(SourceFactoryBeltInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryBeltInfos := NewFactoryBeltInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Gets the level of the factory belt for the player. 
GetFactoryBeltLevel<public>(Player:player, FactoryBeltNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryBeltInfos[FactoryBeltNumber].Level

# Builds the factory mould for the player.
# Also, activates the factory mould.
BuildFactoryMould<public>(Player:player, FactoryMouldNumber:int)<decides><transacts>:void=
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryMouldInfo := SourceInfo.FactoryMouldInfos[FactoryMouldNumber]

    # Fails if mould is already built.
    SourceFactoryMouldInfo.Built = false

    var NewFactoryMouldInfos:[]factory_mould_info = SourceInfo.FactoryMouldInfos
    set NewFactoryMouldInfos[FactoryMouldNumber] = factory_mould_info:
        Built := true
        Active := true
        MakeFactoryMouldInfo<constructor>(SourceFactoryMouldInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryMouldInfos := NewFactoryMouldInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Succeeds if the factory mould is built for the player.
IsFactoryMouldBuilt<public>(Player:player, FactoryMouldNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryMouldInfos[FactoryMouldNumber].Built?

# Sets the activation of the factory mould for the player.
SetFactoryMouldActive<public>(Player:player, FactoryMouldNumber:int, Active:logic)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryMouldInfo := SourceInfo.FactoryMouldInfos[FactoryMouldNumber]

    # Fails if mould is not built.
    SourceFactoryMouldInfo.Built = true

    var NewFactoryMouldInfos:[]factory_mould_info = SourceInfo.FactoryMouldInfos
    set NewFactoryMouldInfos[FactoryMouldNumber] = factory_mould_info:
        Active := Active
        MakeFactoryMouldInfo<constructor>(SourceFactoryMouldInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryMouldInfos := NewFactoryMouldInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Succeeds if the factory mould is active for the player.
IsFactoryMouldActive<public>(Player:player, FactoryMouldNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryMouldInfos[FactoryMouldNumber].Active?

# Upgrades the level of the factory mould for the player.
UpgradeFactoryMouldLevel<public>(Player:player, FactoryMouldNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    var NewFactoryMouldInfos:[]factory_mould_info = SourceInfo.FactoryMouldInfos
    SourceFactoryMouldInfo := SourceInfo.FactoryMouldInfos[FactoryMouldNumber]
    set NewFactoryMouldInfos[FactoryMouldNumber] = factory_mould_info:
        Level := SourceFactoryMouldInfo.Level + 1
        MakeFactoryMouldInfo<constructor>(SourceFactoryMouldInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryMouldInfos := NewFactoryMouldInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Gets the level of the factory mould for the player. 
GetFactoryMouldLevel<public>(Player:player, FactoryMouldNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryMouldInfos[FactoryMouldNumber].Level

# Sets the latest production time of the factory mould for the player.
SetFactoryMouldLatestProductionTime<public>(Player:player, FactoryMouldNumber:int, LatestProductionTime:Verse.Scripts.TimeSystem.date_and_time)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    var NewFactoryMouldInfos:[]factory_mould_info = SourceInfo.FactoryMouldInfos
    SourceFactoryMouldInfo := SourceInfo.FactoryMouldInfos[FactoryMouldNumber]
    set NewFactoryMouldInfos[FactoryMouldNumber] = factory_mould_info:
        LatestProductionTime := LatestProductionTime
        MakeFactoryMouldInfo<constructor>(SourceFactoryMouldInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryMouldInfos := NewFactoryMouldInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Gets the latest production time of the factory mould for the player.
GetFactoryMouldLatestProductionTime<public>(Player:player, FactoryMouldNumber:int)<decides><transacts>:Verse.Scripts.TimeSystem.date_and_time=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryMouldInfos[FactoryMouldNumber].LatestProductionTime

# Builds the factory wrapper for the player.
BuildFactoryWrapper<public>(Player:player, FactoryWrapperNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryWrapperInfo := SourceInfo.FactoryWrapperInfos[FactoryWrapperNumber]

    # Fails if wrapper is already built.
    SourceFactoryWrapperInfo.Built = false

    var NewFactoryWrapperInfos:[]factory_wrapper_info = SourceInfo.FactoryWrapperInfos
    set NewFactoryWrapperInfos[FactoryWrapperNumber] = factory_wrapper_info:
        Built := true
        MakeFactoryWrapperInfo<constructor>(SourceFactoryWrapperInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryWrapperInfos := NewFactoryWrapperInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Succeeds if the factory wrapper is built for the player.
IsFactoryWrapperBuilt<public>(Player:player, FactoryWrapperNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryWrapperInfos[FactoryWrapperNumber].Built?

# Upgrades the level of the factory wrapper for the player.
UpgradeFactoryWrapperLevel<public>(Player:player, FactoryWrapperNumber:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    var NewFactoryWrapperInfos:[]factory_wrapper_info = SourceInfo.FactoryWrapperInfos
    SourceFactoryWrapperInfo := SourceInfo.FactoryWrapperInfos[FactoryWrapperNumber]
    set NewFactoryWrapperInfos[FactoryWrapperNumber] = factory_wrapper_info:
        Level := SourceFactoryWrapperInfo.Level + 1
        MakeFactoryWrapperInfo<constructor>(SourceFactoryWrapperInfo)

    set PlayerInfoMap[Player] = player_info:
        FactoryWrapperInfos := NewFactoryWrapperInfos
        MakePlayerInfo<constructor>(SourceInfo)

# Gets the level of the factory wrapper for the player. 
GetFactoryWrapperLevel<public>(Player:player, FactoryWrapperNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryWrapperInfos[FactoryWrapperNumber].Level

# Builds the factory storage for the player.
BuildFactoryStorage<public>(Player:player)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryStorageInfo := SourceInfo.FactoryStorageInfo

    # Fails if storage is already built.
    SourceFactoryStorageInfo.Built = false

    # Create a new storage info with the Built flag set to true
    var TempStorage:factory_storage_info = factory_storage_info:
        Built := true
        Level := SourceFactoryStorageInfo.Level
        ProductsInStorage := SourceFactoryStorageInfo.ProductsInStorage

    set PlayerInfoMap[Player] = player_info:
        FactoryStorageInfo := TempStorage
        MakePlayerInfo<constructor>(SourceInfo)

# Succeeds if the factory storage is built for the player.
IsFactoryStorageBuilt<public>(Player:player)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryStorageInfo.Built?

# Succeeds if there is any available capacity left in the factory storage for the player.
IsRoomInFactoryStorage<public>(Player:player)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    IsFactoryStorageBuilt[Player]
    GetFactoryStorageCapacity[Player] > GetAllFactoryProducts[Player]

# Succeeds if there are products stored in the factory storage for the player.
IsAnyProductInFactoryStorage<public>(Player:player)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    GetAllFactoryProducts[Player] > 0

# Stores the amount of factory products in the factory storage for the player.
StoreFactoryProduct<public>(Player:player, ProductNumber:int, Amount:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryStorageInfo := SourceInfo.FactoryStorageInfo

    # Fails if there is not room.
    GetAllFactoryProducts[Player] + Amount <= GetFactoryStorageCapacity[Player]

    SourceProductsInStorage := SourceFactoryStorageInfo.ProductsInStorage[ProductNumber]

    var NewProductsInStorage:[]int = SourceFactoryStorageInfo.ProductsInStorage
    set NewProductsInStorage[ProductNumber] = SourceProductsInStorage + Amount

    # Create a new storage info with updated products
    var TempStorage:factory_storage_info = factory_storage_info:
        Built := SourceFactoryStorageInfo.Built
        Level := SourceFactoryStorageInfo.Level
        ProductsInStorage := NewProductsInStorage

    set PlayerInfoMap[Player] = player_info:
        FactoryStorageInfo := TempStorage
        MakePlayerInfo<constructor>(SourceInfo)


# Gets the factory product amount in the factory storage for the player.
GetFactoryProduct<public>(Player:player, ProductNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryStorageInfo.ProductsInStorage[ProductNumber]

# Gets the total amount of factory products in the factory storage for the player.
GetAllFactoryProducts<public>(Player:player)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryStorageInfo := SourceInfo.FactoryStorageInfo

    var TotalProductsInStorage:int = 0
    for(ProductsInStorage : SourceFactoryStorageInfo.ProductsInStorage):
        set TotalProductsInStorage += ProductsInStorage

    TotalProductsInStorage

# Withdraws the amount of factory products from the factory storage for the player.
WithdrawFactoryProduct<public>(Player:player, ProductNumber:int, Amount:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryStorageInfo := SourceInfo.FactoryStorageInfo
    SourceProductsInStorage := SourceFactoryStorageInfo.ProductsInStorage[ProductNumber]

    # Fails if there is not enough of a product.
    SourceProductsInStorage >= Amount

    var NewProductsInStorage:[]int = SourceFactoryStorageInfo.ProductsInStorage
    set NewProductsInStorage[ProductNumber] = SourceProductsInStorage - Amount

    # Create a new storage info with updated products
    var TempStorage:factory_storage_info = factory_storage_info:
        Built := SourceFactoryStorageInfo.Built
        Level := SourceFactoryStorageInfo.Level
        ProductsInStorage := NewProductsInStorage

    set PlayerInfoMap[Player] = player_info:
        FactoryStorageInfo := TempStorage
        MakePlayerInfo<constructor>(SourceInfo)


# Upgrades the level of the factory storage for the player.
UpgradeFactoryStorageLevel<public>(Player:player)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceFactoryStorageInfo := SourceInfo.FactoryStorageInfo

    # Create a new storage info with increased level
    var TempStorage:factory_storage_info = factory_storage_info:
        Built := SourceFactoryStorageInfo.Built
        Level := SourceFactoryStorageInfo.Level + 1
        ProductsInStorage := SourceFactoryStorageInfo.ProductsInStorage

    set PlayerInfoMap[Player] = player_info:
        FactoryStorageInfo := TempStorage
        MakePlayerInfo<constructor>(SourceInfo)


# Gets the level of the factory storage for the player. 
GetFactoryStorageLevel<public>(Player:player)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].FactoryStorageInfo.Level

# Gets the current order number for the player. 
GetOrderNumber<public>(Player:player)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].OrderNumber

GetOrderLineInfoIndex(OrderInfo:order_info, ProductNumber:int)<transacts>:int=
    var OrderLineInfoIndex:int = -1
    for:
        Index := 0..OrderInfo.OrderLineInfos.Length-1
        OrderInfo.OrderLineInfos[Index].ProductNumber = ProductNumber
    do:
        set OrderLineInfoIndex = Index
    OrderLineInfoIndex

# Delivers the amount of factory products from the factory storage for the player.
# The player is granted the amount of gold dictated by the current order. 
DeliverProduct<public>(Player:player, ProductNumber:int, Amount:int)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    SourceOrderInfo := SourceInfo.OrderInfo

    var GoldEarnings:int = 0
    var ProductsNotInOrderDelivered:int = Amount

    var NewOrderLineInfos:[]order_line_info = SourceOrderInfo.OrderLineInfos
    if:
        OrderLineInfoIndex := GetOrderLineInfoIndex(SourceOrderInfo, ProductNumber)
        SourceOrderLineInfo := SourceOrderInfo.OrderLineInfos[OrderLineInfoIndex]
        ProductsLeftInOrder := SourceOrderLineInfo.ProductsOrdered - SourceOrderLineInfo.ProductsDelivered
        ProductsInOrderDelivered := Min(Amount, ProductsLeftInOrder)
        set ProductsNotInOrderDelivered -= ProductsInOrderDelivered

        # Apply multiplier from factory wrapper to base price.
        Multiplier := GetFactoryWrapperMultiplier[ProductNumber, Player]
        set GoldEarnings += Int[ProductsInOrderDelivered * SourceOrderLineInfo.BasePrice * Multiplier]

        set NewOrderLineInfos[OrderLineInfoIndex] = order_line_info:
            ProductsDelivered := SourceOrderLineInfo.ProductsDelivered + ProductsInOrderDelivered
            MakeOrderLineInfo<constructor>(SourceOrderLineInfo)

    set PlayerInfoMap[Player] = player_info:
        OrderInfo := order_info:
            OrderLineInfos := NewOrderLineInfos
            MakeOrderInfo<constructor>(SourceOrderInfo)
        MakePlayerInfo<constructor>(SourceInfo)

    # Any products delivered that are not part of the order will only earn the player a scrap price.
    FactoryProductScrapPrice := GetProductScrapPrice[ProductNumber]
    set GoldEarnings += ProductsNotInOrderDelivered * FactoryProductScrapPrice

    GrantGold[Player, GoldEarnings]

# Gets the delivery price for the factory product in the current order for the player.
GetDeliveryPrice<public>(Player:player, ProductNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    OrderInfo := PlayerInfoMap[Player].OrderInfo

    OrderLineInfoIndex := GetOrderLineInfoIndex(OrderInfo, ProductNumber)

    if:
        OrderLineInfo := OrderInfo.OrderLineInfos[OrderLineInfoIndex]
        OrderLineInfo.ProductsDelivered < OrderLineInfo.ProductsOrdered
        Multiplier := GetFactoryWrapperMultiplier[ProductNumber, Player]
    then:
        # Apply multiplier from factory wrapper to base price.
        Int[OrderLineInfo.BasePrice * Multiplier]
    else:
        # If the order asks for no more of this factory product, list the scrap price. 
        GetProductScrapPrice[ProductNumber]

# Gets the time when the current order was placed for the player.
GetOrderTime<public>(Player:player)<decides><transacts>:Verse.Scripts.TimeSystem.date_and_time=
    CheckPlayerInfoForPlayer[Player]
    PlayerInfoMap[Player].OrderInfo.OrderTime

# Gets the amount of factory products ordered in the current order for the player.
GetProductsOrdered<public>(Player:player, ProductNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    OrderInfo := PlayerInfoMap[Player].OrderInfo

    OrderLineInfoIndex := GetOrderLineInfoIndex(OrderInfo, ProductNumber)

    OrderInfo.OrderLineInfos[OrderLineInfoIndex].ProductsOrdered or 0

# Gets the amount of factory products already delivered to the current order for the player.
GetProductsDelivered<public>(Player:player, ProductNumber:int)<decides><transacts>:int=
    CheckPlayerInfoForPlayer[Player]
    OrderInfo := PlayerInfoMap[Player].OrderInfo

    OrderLineInfoIndex := GetOrderLineInfoIndex(OrderInfo, ProductNumber)

    OrderInfo.OrderLineInfos[OrderLineInfoIndex].ProductsDelivered or 0

# Succeeds if the current order is completed for the player.
IsOrderCompleted<public>(Player:player)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    OrderInfo := PlayerInfoMap[Player].OrderInfo

    for:
        OrderLineInfo : OrderInfo.OrderLineInfos
        OrderLineInfo.ProductsDelivered < OrderLineInfo.ProductsOrdered
    do:
        false?

# Places a new order for the player.
PlaceNewOrder<public>(Player:player, OrderTime:Verse.Scripts.TimeSystem.date_and_time)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]
    OrderNumber := SourceInfo.OrderNumber

    # Create an order based on the player's progression and the products that can be produced.
    Order := GetOrder[Player]

    var OrderLineInfos:[]order_line_info = array{}
    for(OrderLine : Order):
        OrderLineInfo :=  order_line_info:
            ProductNumber := OrderLine.ProductNumber
            ProductsOrdered := OrderLine.ProductsOrdered
            BasePrice := OrderLine.BasePrice
        set OrderLineInfos += array{OrderLineInfo}

    OrderInfo := order_info:
        OrderLineInfos := OrderLineInfos
        OrderTime := OrderTime

    set PlayerInfoMap[Player] = player_info:
            OrderNumber := OrderNumber + 1
            OrderInfo := OrderInfo
            MakePlayerInfo<constructor>(SourceInfo)

# Sets the gift opening time for the player.
SetGiftOpeningTime<public>(Player:player, NewGiftOpeningTime:Verse.Scripts.TimeSystem.date_and_time)<decides><transacts>:void=
    CheckPlayerInfoForPlayer[Player]
    SourceInfo := PlayerInfoMap[Player]

    # Professor Flopkins: "Oh no, someone forgot to set the gift opening time!"
    #                     "We should make a new player_info and set the gift opening time."
    #                     "Check out SetCandyCaneHarvestTime or GrantGold to get an idea."
    set PlayerInfoMap[Player] = player_info:
        GiftOpeningTime := NewGiftOpeningTime


# Gets the gift opening time for the player. 
GetGiftOpeningTime<public>(Player:player)<decides><transacts>:Verse.Scripts.TimeSystem.date_and_time=
    CheckPlayerInfoForPlayer[Player]

    # Professor Flopkins: "Yikes, looks like the returned date and time has nothing to do with the"
    #                     "persistable player data."
    #Verse.Scripts.TimeSystem.date_and_time{}
    PlayerInfoMap[Player].GiftOpeningTime

    # Professor Flopkins: "We should retrieve it from the player info map instead!"
    #                     "Look into GetCandyCaneHarvestTime or GetGold for inspiration."
Sign in to download module

Copy-paste each file above is always free.

Comments

    Sign in to vote, comment, or suggest an edit. Sign in
    📄
    Source
    @jukebox
    Epic Games
    @jukebox

    © Epic Games. From an official Epic Games starter project / UEFN sample, shown here as reference for study with a link to Epic's docs. All rights remain with Epic Games — this is not Verse Island original work. Terms ↗

    View original Sources & licensing
    Request removal
    Tags
    epic-sample epic reference module tycoon game-loop factory persistence
    Verse module (5 files)
    Not yet compile-verified

    Download the full module (zip) from the code panel above.

    Keep exploring
    More in Materials/VFX · 9 of 13
    Prev
    fortnite vfx toggle device
    Next
    fortnite material function glitch
    Related topics
    • Santas Toy Factory Player Persistence Player
    • Santas Toy Factory Order Config Verse Language
    • Santas Toy Factory Upgrade Config Verse Language
    • Lego Santas Toy Factory In Unreal Editor For Fortnite Tutorials
    • Lego Santas Toy Factory In Unreal Editor For Fortnite Tutorials

    Related

    Open in graph →

    Related topics

    • Santas Toy Factory Player Persistence
    • Santas Toy Factory Order Config
    • Santas Toy Factory Upgrade Config
    • Lego Santas Toy Factory In Unreal Editor For Fortnite
    • Lego Santas Toy Factory In Unreal Editor For Fortnite
    VerseIsland · an archipelago of Verse & UEFN knowledge
    🗺️ Island atlas Learn Guides History About & Press Sources & Licensing Status

    Not affiliated with Epic Games. Fortnite, UEFN, Unreal Engine, and Verse are trademarks of Epic Games, Inc. Content is attributed to its source — see Sources & Licensing.

    🦜

    Scout · your island guide

    The Isle of Verse