using { /Fortnite.com/UI }
using { /Fortnite.com/Devices }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
StatTableModule := module:
@editable
TT_Stats<localizes> : message = "Stats will appear on the table, in the order they're defined."
TT_StatIndexOrdering<localizes> : message = "Determines which one of the stats will be used for ordering on the stat table."
StringToMessage<localizes>(String : string) : message = "{String}"
PlayerToMessage<localizes>(Player : player) : message = "{Player}"
stat_data := class<concrete>:
@editable
StatName : string = ""
@editable
ColumnWidth : float = 0.0
@editable
DigitsToDisplayAfterTheDecimal : int = 0
@editable
HeaderTextColor : color = NamedColors.White
@editable
ValueTextColor : color = NamedColors.White
stat_update_data := class<concrete>:
@editable
StatIndex : int = 0
@editable
UpdateAmount : float = 0.0
@editable
UpdateTrigger : trigger_device = trigger_device{}
var MaybeStatTableDevice : ?stat_table_device = false
Init(StatTableDevice : stat_table_device):void=
set MaybeStatTableDevice = option{ StatTableDevice }
UpdateTrigger.TriggeredEvent.Subscribe(OnUpdateTriggered)
OnUpdateTriggered(MaybeAgent : ?agent):void=
if (Agent := MaybeAgent?, StatTableDevice := MaybeStatTableDevice?) :
StatTableDevice.OnUpdateTriggered(Agent, Self)
agentt_data := class:
var StatValues : [string]float = map{}
var MaybeStatTableCanvas : ?canvas = false
stat_table_device := class(creative_device):
@editable
PlayerSpawners : []player_spawner_device = array{}
@editable {ToolTip := TT_Stats }
Stats : []stat_data = array{}
@editable
StatUpdates : []stat_update_data = array{}
@editable{ ToolTip := TT_StatIndexOrdering }
StatIndexForOrdering : int = 0
@editable
StatTableAnchor : vector2 = vector2{ X := 1.0, Y := 0.0 }
@editable
StatTableAlignment : vector2 = vector2{ X := 1.0, Y := 0.0 }
@editable
StatTableOffsets : vector2 = vector2{ X := -32.0, Y := -128.0 }
@editable
ValueRowCount : int = 5
@editable
HeaderRowHeight : float = 40.0
@editable
ValueRowHeight : float = 40.0
@editable
OrderColumnWidth : float = 20.0
@editable
PlayerNameColumnWidth : float = 60.0
@editable
BGColor : color = NamedColors.Black
@editable
BGOpacity : type {_X:float where 0.000000 <= _X, _X <= 1.000000} = 0.35
@editable
LineThickness : float = 4.0
@editable
LineColor : color = NamedColors.White
@editable
OrderColumnValueTextColor : color = NamedColors.White
@editable
PlayerNameColumnHeaderTextColor : color = NamedColors.White
@editable
PlayerNameColumnValueTextColor : color = NamedColors.White
var AgentMap : [agent]agentt_data = map{}
var ValueTextsSlotStartIndex : int = 0
var StatCount : int = 0
OnBegin<override>()<suspends>:void=
Print("OnBegin")
UpdateStatTableCanvas()
set StatCount = Stats.Length
for (Index -> StatUpdate : StatUpdates) :
Print("Index: {Index}")
StatUpdate.Init(Self)
for (Player : GetPlayspace().GetPlayers()) :
OnPlayerSpawned(Player)
for (PlayerSpawner : PlayerSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawned)
OnPlayerSpawned(Agent : agent):void =
Print("OnPlayerSpawned")
if (not AgentMap[Agent], Player := player[Agent], PlayerUI := GetPlayerUI[Player]):
StatTableCanvas := CreateStatTableCanvas()
AgentData := agentt_data{}
for (Stat : Stats):
if (set AgentData.StatValues[Stat.StatName] = 0.0):
set AgentData.MaybeStatTableCanvas = option{ StatTableCanvas }
if (set AgentMap[Agent] = AgentData):
PlayerUI.AddWidget(StatTableCanvas)
UpdateStatTableCanvas()
OnUpdateTriggered(Agent : agent, StatUpdateData : stat_update_data):void =
Print("OnUpdateTriggered {StatUpdateData.StatIndex}")
if (AgentData := AgentMap[Agent], Stat := Stats[StatUpdateData.StatIndex]):
if (set AgentData.StatValues[Stat.StatName] += StatUpdateData.UpdateAmount):
UpdateStatTableCanvas()
UpdateStatTableCanvas():void =
Print("UpdateStatTableCanvas")
StatNameToOrder := if (String := Stats[StatIndexForOrdering].StatName)
then String
else ""
Players := GetPlayspace().GetPlayers()
var PlayerStatValues : []float = for (Index -> Player : Players, AgentData := AgentMap[Player],
StatValue := AgentData.StatValues[StatNameToOrder]):
StatValue
var OrderedPlayerIndexes : []int = array{}
for (I := 1 .. ValueRowCount):
MaxIndex := FindMaxIndex(PlayerStatValues)
Print("MaxIndex: {MaxIndex}")
if (MaxValue := PlayerStatValues[MaxIndex], MaxValue >= 0.0):
Print("MaxIndex: {MaxIndex}")
set OrderedPlayerIndexes += array{ MaxIndex }
if (set PlayerStatValues[MaxIndex] = -1.0) {}
for (Player : Players,AgentData := AgentMap[Player],StatTableCanvas := AgentData.MaybeStatTableCanvas?,
StatTableInnerCanvas := canvas[StatTableCanvas.Slots[0].Widget]):
var CurrentSlotIndex : int = ValueTextsSlotStartIndex
ColumnCount := StatCount + 2
for (I := 0 .. ValueRowCount - 1):
# (frozenpawn): Fill the messages to display for this row.
var Messages : []message = array {}
if (OrderedPlayerIndex := OrderedPlayerIndexes[I],OrderedPlayer := Players[OrderedPlayerIndex],
OrderedAgentData := AgentMap[OrderedPlayer]):
set Messages = array {StringToMessage("#{I+1}"),PlayerToMessage(OrderedPlayer)}
set Messages += for (Stat : Stats,StatValue := OrderedAgentData.StatValues[Stat.StatName]):
StringToMessage("{AbbreviateNumberText(StatValue, Stat.DigitsToDisplayAfterTheDecimal)}")
for (ColumnIndex := 0 .. ColumnCount - 1,TextBlock := text_block[StatTableInnerCanvas.Slots[CurrentSlotIndex].Widget]):
MessageToSet := if (Msg := Messages[ColumnIndex])
then Msg
else StringToMessage("")
TextBlock.SetText(MessageToSet)
set CurrentSlotIndex += 1
CreateStatTableCanvas():canvas=
return canvas:
Slots := array:
canvas_slot:
Anchors := anchors { Minimum := StatTableAnchor, Maximum := StatTableAnchor }
Offsets := margin { Top := StatTableOffsets.Y, Left := StatTableOffsets.X, Right := 0.0, Bottom := 0.0 }
Alignment := StatTableAlignment
SizeToContent := true
ZOrder := 0
Widget := CreateStatTableCanvasInner()
CreateStatTableCanvasInner():canvas=
BGHeight := HeaderRowHeight + ValueRowCount*ValueRowHeight
var BGWidth : float = OrderColumnWidth + PlayerNameColumnWidth
for (Stat : Stats):
set BGWidth += Stat.ColumnWidth
var CanvasSlots : []canvas_slot = array
{
CanvasSlot0 := canvas_slot:
Anchors := anchors{ Minimum := vector2{X := 0.0, Y := 0.0}, Maximum := vector2{X := 0.0, Y := 0.0} }
Offsets := margin{ Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0 }
Alignment := vector2{X := 0.0, Y := 0.0}
ZOrder := 0
Widget := color_block{ DefaultColor := BGColor, DefaultOpacity := BGOpacity, DefaultDesiredSize := vector2{X := BGWidth, Y := BGHeight} }
}
# (frozenpawn): Horizontal Line.
HorizontalLineLength := BGWidth
set CanvasSlots += array
{
CanvasSlot1 := canvas_slot:
Anchors := anchors{ Minimum := vector2{X := 0.0, Y := 0.0}, Maximum := vector2{X := 0.0, Y := 0.0} }
Offsets := margin{ Top := HeaderRowHeight, Left := 0.0, Right := 0.0, Bottom := 0.0 }
Alignment := vector2{X := 0.0, Y := 0.0}
ZOrder := 1
Widget := color_block{ DefaultColor := LineColor, DefaultDesiredSize := vector2{X := HorizontalLineLength, Y := LineThickness} }
}
# (frozenpawn): Vertical Lines.
var TotalLeftOffset : float = 0.0
var ColumnWidths : []float = array { OrderColumnWidth, PlayerNameColumnWidth }
set ColumnWidths += for (Stat : Stats):
Stat.ColumnWidth
LineHeight := BGHeight
for (I := 0 .. StatCount, ColumnWidth := ColumnWidths[I]):
set TotalLeftOffset += ColumnWidth
Print("Vertical Line {I}, Offset: {TotalLeftOffset}, Width: {ColumnWidth}")
set CanvasSlots += array
{
CanvasSlot2 := canvas_slot:
Anchors := anchors{ Minimum := vector2{X := 0.0, Y := 0.0}, Maximum := vector2{X := 0.0, Y := 0.0} }
Offsets := margin{ Top := 0.0, Left := TotalLeftOffset, Right := 0.0, Bottom := 0.0 }
Alignment := vector2{X := 0.5, Y := 0.0}
ZOrder := 1
Widget := color_block{ DefaultColor := LineColor, DefaultDesiredSize := vector2{X := LineThickness, Y := LineHeight} }
}
# Header Texts
set TotalLeftOffset = OrderColumnWidth
var HeaderStrings : []string = array { "Player Name" }
var HeaderStringColors: []color = array { PlayerNameColumnHeaderTextColor }
set HeaderStrings += for (Stat : Stats):
Stat.StatName
set HeaderStringColors += for (Stat : Stats):
Stat.HeaderTextColor
for (I := 0 .. StatCount, HeaderString := HeaderStrings[I], HeaderStringColor := HeaderStringColors[I],
ColumnWidth := ColumnWidths[I+1]):
LeftOffset := TotalLeftOffset + ColumnWidth * 0.5
Print("Header String {I}: {HeaderString}, Offset: {LeftOffset}, Width: {ColumnWidth}")
set CanvasSlots += array
{
CanvasSlot3 := canvas_slot:
Anchors := anchors { Minimum := vector2{X := 0.0, Y := 0.0}, Maximum := vector2{X := 0.0, Y := 0.0} }
Offsets := margin { Top := HeaderRowHeight * 0.5, Left := LeftOffset, Right := 0.0, Bottom := 0.0 }
Alignment := vector2 { X := 0.5, Y := 0.5 }
ZOrder := 1
Widget := text_block {DefaultText := StringToMessage(HeaderString), DefaultTextColor := HeaderStringColor}
}
set TotalLeftOffset += ColumnWidth
set ValueTextsSlotStartIndex = CanvasSlots.Length
# (frozenpawn): Value TextBlocks.
var ValueStringColors : []color = array{ OrderColumnValueTextColor, PlayerNameColumnValueTextColor }
set ValueStringColors += for (Stat : Stats):
Stat.ValueTextColor
for (I := 0 .. ValueRowCount-1):
VerticalOffset := HeaderRowHeight * (ValueRowHeight*I) + ValueRowHeight*0.5
set TotalLeftOffset = 0.0
for (Index -> ColumnWidth: ColumnWidths, ValueStringColor := ValueStringColors[Index]):
LeftOffset := TotalLeftOffset + ColumnWidth*0.5
set CanvasSlots += array
{
CanvasSlot4 := canvas_slot:
Anchors := anchors{ Minimum := vector2{X := 0.0, Y := 0.0}, Maximum := vector2{X := 0.0, Y := 0.0} }
Offsets := margin{ Top := VerticalOffset, Left := LeftOffset, Right := 0.0, Bottom := 0.0 }
Alignment := vector2{X := 0.5, Y := 0.5}
ZOrder := 1
Widget := text_block{ DefaultText := StringToMessage(""), DefaultTextColor := ValueStringColor }
}
set TotalLeftOffset += ColumnWidth
return canvas:
Slots := CanvasSlots
FloatToString(Num : float, DigitsAfterTheDecimal : int)<transacts>:string=
var String : string := "NaN"
if (NumInt := Int[Num]):
set String = "{NumInt}"
if (DigitsAfterTheDecimal > 0):
set String += "."
for (I := 1 .. DigitsAfterTheDecimal):
Pow10 := Pow(10.0, I*1.0)
NumIntPow10 := NumInt*Pow10
NumPow10 := Num*Pow10
if (NumInPow10Int := Int [NumIntPow10], NumPow10Int := Int [NumPow10],
Diff := NumPow10Int - NumInPow10Int, Digit := Mod [Diff, 10]):
set String += "{Digit}"
return String
AbbreviateNumberText<public>(Number : float, DigitsAfterDecimalPoint : int):string=
var ResultText : string = ToString(Number)
Abbreviations : []tuple(float,string) = array
{
(1000.0, "K"),
(1000000.0, "M"),
(1000000000.0, "B"),
(1000000000000.0, "T"),
(1000000000000000.0, "Qa"),
(1000000000000000000.0, "Qi")
}
AbbreviationCount : int = Abbreviations.Length-1
var CurrBackIndex : int = AbbreviationCount
for(i:=0..AbbreviationCount):
if(CurrTuple := Abbreviations[CurrBackIndex]):
if(Abs(Number) >= CurrTuple(0)):
RoundedNumber : float = Number / CurrTuple(0)
set ResultText = FloatToString(RoundedNumber, DigitsAfterDecimalPoint) + CurrTuple(1)
return ResultText
set CurrBackIndex -= 1
if(CeiledValue := Ceil[Number]):
set ResultText = ToString(CeiledValue)
return ResultText
FindMaxIndex<public>(Arr : []float):int=
var MaxIndex : int = -1
if (FirstValue := Arr[0]):
var MaxValue : float = FirstValue
set MaxIndex = 0
var I : int = 1
loop:
if (Value := Arr[I]):
if (MaxValue < Value):
set MaxValue = Value
set MaxIndex = I
else:
break
set I += 1
return MaxIndex
##########################################################################
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
var PlayerCurrencies : weak_map(player, currency_data) = map{}
Persistable class for currencies
currency_data := class:
Coins : int = 0
Rebirths : int = 0
FirstLogin : int = 0
LastLogin : int = 0
persistent_currency_manager := class(creative_device):
@editable
var coinsHUD: hud_message_device = hud_message_device{}
@editable
var rebirthHUD: hud_message_device = hud_message_device{}
# Helper function to convert string to message
StringToMessage<localizes>(InString : string) : message = "{InString}"
getCurrency(Player:player):currency_data=
if(not Player.IsActive[]):
return currency_data{}
else:
newBlankCurrency:= currency_data{}
if(currency:= PlayerCurrencies[Player]):
return currency
else:
return newBlankCurrency
InitializePlayerDataNonSuspended(Player:player):void =
spawn{InitializePlayerDataSuspended(Player)}
# Ensure data is initialized for the player
InitializePlayerDataSuspended(Player:player)<suspends>:void =
Sleep(1.0)
if (not PlayerCurrencies[Player]):
#Print("##################Data does not exist for player, creating new#####################")
if (set PlayerCurrencies[Player] = currency_data{}):
setFirstLoginTime(Player)
spawn{saveLastLoginLoop(Player)}
addCurrency(Player, 500, 0)
#Print("##########################Created new currency for user##########################")
else:
spawn{saveLastLoginLoop(Player)}
addCurrency(Player, 0, 0)
saveLastLoginLoop(Player:player)<suspends>:void=
loop:
Sleep(30.0)
if(Player.IsActive[]):
setLastLoginTime(Player)
else. break
OnPlayerRemoved(Player:player):void =
#Print("Player removed, setting las login time")
setLastLoginTime(Player)
# Helper: Get current time as int
GetCurrentTimeInt():int =
if (TimeInt := Int[GetSecondsSinceEpoch()]):
TimeInt
else:
0
setFirstLoginTime(Player:player):void=
if (Data := PlayerCurrencies[Player]):
if (Data.FirstLogin = 0):
NewData := currency_data{
Coins := Data.Coins,
Rebirths := Data.Rebirths,
FirstLogin := GetCurrentTimeInt(),
LastLogin := Data.LastLogin
}
if (set PlayerCurrencies[Player] = NewData) {
#Print("First login time is {NewData.FirstLogin}")
}
setLastLoginTime(Player:player):void=
if(Player.IsActive[]):
if (Data := PlayerCurrencies[Player]):
NewData := currency_data{
Coins := Data.Coins,
Rebirths := Data.Rebirths,
FirstLogin := Data.FirstLogin,
LastLogin := GetCurrentTimeInt()
}
if (set PlayerCurrencies[Player] = NewData) {
#Print("Last login time is {NewData.LastLogin}")
}
# Combined setter for Coins and Rebirths
zeroCoins(Player:player):void=
if (OldData := PlayerCurrencies[Player]):
NewData := currency_data{
Coins := 1000,
Rebirths := OldData.Rebirths,
FirstLogin := OldData.FirstLogin,
LastLogin := OldData.LastLogin
}
if (set PlayerCurrencies[Player] = NewData, Agent:= agent[Player]){}
addCurrency(Player:player, NewCoins:int, NewRebirths:int):void =
if (OldData := PlayerCurrencies[Player]):
NewData := currency_data{
Coins := OldData.Coins + NewCoins,
Rebirths := OldData.Rebirths + NewRebirths,
FirstLogin := OldData.FirstLogin,
LastLogin := OldData.LastLogin
}
if (set PlayerCurrencies[Player] = NewData, Agent:= agent[Player]) {
if(currentCoins:= PlayerCurrencies[Player].Coins, currentRebirths:= PlayerCurrencies[Player].Rebirths):
coinsMessage:= StringToMessage("{suffixAdd(currentCoins)}")
rebirthMessage:= StringToMessage("{suffixAdd(currentRebirths)}")
#Print("###################################### New saved coins are {currentCoins}##############################")
coinsHUD.Show(Agent, coinsMessage)
rebirthHUD.Show(Agent, rebirthMessage)
}
# Combined setter for Coins and Rebirths
deductCurrency(Player:player, deductCoins:int, NewRebirths:int):void =
if (OldData := PlayerCurrencies[Player]):
NewData := currency_data{
Coins := OldData.Coins - deductCoins,
Rebirths := OldData.Rebirths,
FirstLogin := OldData.FirstLogin,
LastLogin := OldData.LastLogin
}
if (set PlayerCurrencies[Player] = NewData, Agent:= agent[Player]) {
if(currentCoins:= PlayerCurrencies[Player].Coins):
coinsMessage:= StringToMessage("{suffixAdd(currentCoins)}")
#Print("######################################New saved coins are {currentCoins}#############################")
coinsHUD.Show(Agent, coinsMessage)
}
suffixAdd(InputNum:int):string=
Trillion:float = 1000000000000.0
Billion:float = 1000000000.0
Million:float = 1000000.0
Thousand: float = 1000.0
Hundreads: float = 100.0
Tens: float = 10.0
var InputNumFloat:float = InputNum*1.0
var suffix:string=""
var finalNum:float=0.0
var length:int = 1
if(InputNumFloat>= Trillion):
set suffix = "T"
set finalNum = InputNumFloat/Trillion
set length= 4
else if(InputNumFloat>= Billion):
set suffix = "B"
set finalNum = InputNumFloat/Billion
set length= 4
else if(InputNumFloat>= Million):
set suffix = "M"
set finalNum = InputNumFloat/Million
set length= 4
else if(InputNumFloat>= Thousand):
set suffix = "K"
set finalNum = InputNumFloat/Thousand
set length= 4
else if(InputNumFloat>= Hundreads):
set finalNum = InputNumFloat
set suffix = ""
set length= 3
else if(InputNumFloat>= Tens):
set finalNum = InputNumFloat
set suffix = ""
set length= 2
else:
set finalNum = InputNumFloat
set suffix = ""
set length= 1
return SplitString(ToString(finalNum), suffix, length)
SplitString(InputString:string, Suffix:string, length:int) : string =
StringLength := InputString.Length
var returnString:string = "Failed"
if:
tempString:=InputString.Slice[0, length] + ToString(Suffix)
set returnString = tempString
return returnString
# Handle new player joining
OnPlayerAdded(Player:player):void =
Print("@@@@@@@@@@@@@@@@@@@@@Currencyyyyyyyy@@@@@@@@@@@@@@@@@@@@@@@@@")
InitializePlayerDataNonSuspended(Player)
OnBegin<override>()<suspends>:void =
# Subscribe to new player joins
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
Players := GetPlayspace().GetPlayers()
for (Player : Players):
InitializePlayerDataNonSuspended(Player)
I want to get data directly from persistent_currency_manager instead of Trigger, but I want TRIGGER to remain an option for me to specify. I am currently confused and do not know how to connect them.