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.