mirror of
https://github.com/TemujinCalidius/SurvivorCore.git
synced 2026-08-14 00:58:01 +00:00
Redesign survival HUD: panel, collapsible view, numeric values, icon slots
Wraps the bars in a dark translucent Panel and splits them into a Primary group (Health, Energy — always shown) and a Collapsible group (the other five) toggled by a chevron button, wired generically in the binder via name/attribute/tag so the owner can re-group by drag-and-drop. Each bar gains an optional Value label (configurable fraction/percent/value/none) and a blank, hidden Icon ImageLabel (resolved from a per-bar attribute, the stat config, or Assets "StatIcons" — the engine still ships zero asset ids). Sets IgnoreGuiInset=false so the HUD clears the Roblox topbar. Adds icon/valueFormat fields through StatDefs/StatConfig + the config template, and a silent Assets.tryGet for the icon hot path. The fallback HUD matches. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
772b555578
commit
c51e4b899b
+3
-1
@@ -12,7 +12,9 @@ is promoted to the new version and `main` is tagged `vX.Y.Z`.
|
||||
drain/regen, stored as auto-replicating Player Attributes, and a reactive **top-left HUD**
|
||||
renders them (no RemoteEvents). The HUD is a real, designer-editable `SurvivalHud` ScreenGui
|
||||
in StarterGui — restyle it in Studio with zero code; bars bind by a `Stat` attribute and a
|
||||
`Fill` child. Ships with seven default stats (health/energy/hunger/thirst/fatigue/blood/
|
||||
`Fill` child. It's a translucent panel that shows **Health + Energy** by default and **expands**
|
||||
to the full roster, with per-bar **numeric readouts** (`99/100` / `%`, configurable) and blank
|
||||
**icon slots** (filled from the `Assets` registry — the engine ships none). Ships with seven default stats (health/energy/hunger/thirst/fatigue/blood/
|
||||
poison), tunable **without code** via a `SurvivalStatsConfig` Configuration instance (or, for
|
||||
developers, `Config.override("SurvivalStats", …)` / `Stats.defineStat`). Works out of the box
|
||||
for every distribution — the demo/Rojo source mount it, the drop-in `.rbxm` auto-installs it
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"Start": 100,
|
||||
"WarnAt": 25,
|
||||
"Invert": false,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -22,7 +24,9 @@
|
||||
"Start": 100,
|
||||
"WarnAt": 25,
|
||||
"Invert": false,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34,7 +38,9 @@
|
||||
"Start": 0,
|
||||
"WarnAt": 25,
|
||||
"Invert": true,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -46,7 +52,9 @@
|
||||
"Start": 0,
|
||||
"WarnAt": 25,
|
||||
"Invert": true,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -58,7 +66,9 @@
|
||||
"Start": 0,
|
||||
"WarnAt": 25,
|
||||
"Invert": true,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -70,7 +80,9 @@
|
||||
"Start": 100,
|
||||
"WarnAt": 25,
|
||||
"Invert": false,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -82,7 +94,9 @@
|
||||
"Start": 0,
|
||||
"WarnAt": 25,
|
||||
"Invert": true,
|
||||
"Display": true
|
||||
"Display": true,
|
||||
"Icon": "",
|
||||
"ValueFormat": "fraction"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+1522
-717
File diff suppressed because it is too large
Load Diff
+161
-4
@@ -8,12 +8,27 @@
|
||||
|
||||
Per-bar contract (attributes on the tagged Frame; most default from the stat config,
|
||||
so owners usually set only `Stat`):
|
||||
• Stat (string) — Player attribute to bind, e.g. "Hunger" (required)
|
||||
• Max (number) — fill denominator
|
||||
• Invert (bool) — fill = 1 - value/Max (bar full = healthy)
|
||||
• WarnAt (number) — warn when the displayed bar drops below this percent
|
||||
• Stat (string) — Player attribute to bind, e.g. "Hunger" (required)
|
||||
• Max (number) — fill denominator
|
||||
• Invert (bool) — fill = 1 - value/Max (bar full = healthy)
|
||||
• WarnAt (number) — warn when the displayed bar drops below this percent
|
||||
• FillColor/WarnColor (Color3), FillAxis ("X"|"Y")
|
||||
• ValueFormat (string) — numeric readout: "fraction" "99/100" | "percent" "99%"
|
||||
| "value" "99" | "none" (hide). Defaults to the stat config.
|
||||
• Icon (string) — icon asset id override (defaults to the stat config / Assets).
|
||||
Required child: a GuiObject named `Fill`. Everything else is free-form styling.
|
||||
|
||||
OPTIONAL per-bar children the binder also drives if present (absent => ignored):
|
||||
• TextLabel `Value` — filled with the formatted number (per ValueFormat above).
|
||||
• ImageLabel `Icon` — Image set from the resolved icon id; hidden while the id is "".
|
||||
|
||||
OPTIONAL collapse controls (so the default view shows only the primary bars and the
|
||||
rest reveal on click) — all discovered by name/attribute/tag, never by fixed path:
|
||||
• A GuiButton with a `HudToggle` attribute (or tagged "HudToggle") toggles a
|
||||
collapsible container. The attribute's string value names the container; empty
|
||||
falls back to a "Collapsible"-named / "HudCollapsible"-tagged GuiObject.
|
||||
• The container starts Visible=false; the binder flips it and rotates the chevron.
|
||||
Everything new is optional, so a minimal HUD (or the fallback) binds exactly as before.
|
||||
]]
|
||||
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
@@ -24,12 +39,53 @@ local RunService = game:GetService("RunService")
|
||||
assert(RunService:IsClient(), "SurvivorCore HUD is client-only — boot it via SurvivorCore.startClient()")
|
||||
|
||||
local StatConfig = require(script.Parent.Parent.stats.StatConfig)
|
||||
local Assets = require(script.Parent.Parent.foundation.Assets)
|
||||
local HudFallback = require(script.Parent.HudFallback)
|
||||
|
||||
local BAR_TAG = "SurvivorStatBar"
|
||||
local HUD_NAME = "SurvivalHud"
|
||||
local FALLBACK_WAIT = 1 -- seconds to wait for an authored HUD before building the fallback
|
||||
local DEFAULT_WARN_COLOR = Color3.fromRGB(200, 40, 40)
|
||||
local DEFAULT_VALUE_FORMAT = "fraction"
|
||||
local ICON_CATEGORY = "StatIcons" -- Assets category an owner can populate (key = stat name)
|
||||
|
||||
-- Collapse-control discovery (attribute primary, CollectionService tag as a rename-proof
|
||||
-- fallback the owner can apply in Studio; the shipped template uses attributes only).
|
||||
local TOGGLE_NAME = "Toggle"
|
||||
local TOGGLE_TAG = "HudToggle"
|
||||
local TOGGLE_ATTR = "HudToggle"
|
||||
local COLLAPSIBLE_NAME = "Collapsible"
|
||||
local COLLAPSIBLE_TAG = "HudCollapsible"
|
||||
local COLLAPSIBLE_ATTR = "HudCollapsible"
|
||||
|
||||
-- Resolve the bar's numeric readout string for the given raw value/max. `value` is the
|
||||
-- RAW stat (not the inverted fill ratio), so an inverted Hunger bar still reads "12/100".
|
||||
local function formatValue(format: string, value: any, max: number): string
|
||||
local n = if typeof(value) == "number" then value else 0
|
||||
if format == "none" then
|
||||
return ""
|
||||
elseif format == "percent" then
|
||||
local pct = if max > 0 then math.floor((n / max) * 100 + 0.5) else 0
|
||||
return string.format("%d%%", math.clamp(pct, 0, 100))
|
||||
elseif format == "value" then
|
||||
return tostring(math.floor(n + 0.5))
|
||||
end
|
||||
-- "fraction" (default / unknown)
|
||||
return string.format("%d/%d", math.floor(n + 0.5), math.floor(max + 0.5))
|
||||
end
|
||||
|
||||
-- Resolve a bar's icon id: per-bar `Icon` attribute > stat config `icon` > Assets registry
|
||||
-- (category "StatIcons", key = stat name). Returns "" when none — never a hardcoded id.
|
||||
local function resolveIcon(bar: Instance, statName: string, def: any): string
|
||||
local id = bar:GetAttribute("Icon")
|
||||
if typeof(id) == "string" and id ~= "" then
|
||||
return id
|
||||
end
|
||||
if def and typeof(def.icon) == "string" and def.icon ~= "" then
|
||||
return def.icon
|
||||
end
|
||||
return Assets.tryGet(ICON_CATEGORY, statName) -- silent: "" when unregistered
|
||||
end
|
||||
|
||||
local Hud = {}
|
||||
local started = false
|
||||
@@ -89,6 +145,10 @@ function Hud.start(_options: { [string]: any }?)
|
||||
end
|
||||
end
|
||||
|
||||
-- Optional per-bar extras (resolved once; absent => the binder no-ops on them).
|
||||
local valueLabel = bar:FindFirstChild("Value")
|
||||
local iconLabel = bar:FindFirstChild("Icon")
|
||||
|
||||
local function render()
|
||||
local def = byName[statName]
|
||||
local max = bar:GetAttribute("Max") or (def and def.max) or 100
|
||||
@@ -115,6 +175,24 @@ function Hud.start(_options: { [string]: any }?)
|
||||
fillObject.Size = UDim2.new(fillRatio, 0, size.Y.Scale, size.Y.Offset)
|
||||
end
|
||||
setColor(if fillRatio * 100 < warnAt then warnColor else normalColor)
|
||||
|
||||
-- Numeric readout (optional `Value` TextLabel). Uses the RAW value, not the
|
||||
-- inverted fill — owners expect "99/100" to mean the real attribute.
|
||||
if valueLabel and valueLabel:IsA("TextLabel") then
|
||||
local format = bar:GetAttribute("ValueFormat")
|
||||
if typeof(format) ~= "string" then
|
||||
format = (def and def.valueFormat) or DEFAULT_VALUE_FORMAT
|
||||
end
|
||||
valueLabel.Text = formatValue(format, value, max)
|
||||
end
|
||||
|
||||
-- Icon (optional `Icon` ImageLabel). Blank id => hidden, so a fresh engine
|
||||
-- shows no empty box until the owner supplies art.
|
||||
if iconLabel and (iconLabel:IsA("ImageLabel") or iconLabel:IsA("ImageButton")) then
|
||||
local imageId = resolveIcon(bar, statName, def);
|
||||
(iconLabel :: ImageLabel).Image = imageId;
|
||||
(iconLabel :: GuiObject).Visible = imageId ~= ""
|
||||
end
|
||||
end
|
||||
|
||||
local connection = localPlayer:GetAttributeChangedSignal(statName):Connect(render)
|
||||
@@ -140,14 +218,83 @@ function Hud.start(_options: { [string]: any }?)
|
||||
return instance:IsA("GuiObject") and instance:GetAttribute("Stat") ~= nil
|
||||
end
|
||||
|
||||
-- A collapse toggle = a GuiButton with a `HudToggle` attribute (or tagged "HudToggle").
|
||||
local function isToggle(instance: Instance): boolean
|
||||
if not instance:IsA("GuiButton") then
|
||||
return false
|
||||
end
|
||||
return instance:GetAttribute(TOGGLE_ATTR) ~= nil
|
||||
or instance.Name == TOGGLE_NAME
|
||||
or CollectionService:HasTag(instance, TOGGLE_TAG)
|
||||
end
|
||||
|
||||
-- Find the collapsible container a toggle controls. Prefer the container named by the
|
||||
-- toggle's `HudToggle` attribute (searched from the toggle's HUD root), else fall back
|
||||
-- to a "HudCollapsible"-tagged / `HudCollapsible`-attributed / "Collapsible"-named one.
|
||||
local function findCollapsible(button: Instance): Instance?
|
||||
local hudRoot = button:FindFirstAncestorWhichIsA("ScreenGui") or playerGui
|
||||
local named = button:GetAttribute(TOGGLE_ATTR)
|
||||
if typeof(named) == "string" and named ~= "" then
|
||||
local found = hudRoot:FindFirstChild(named, true)
|
||||
if found and found:IsA("GuiObject") then
|
||||
return found
|
||||
end
|
||||
end
|
||||
for _, tagged in CollectionService:GetTagged(COLLAPSIBLE_TAG) do
|
||||
if tagged:IsDescendantOf(hudRoot) and tagged:IsA("GuiObject") then
|
||||
return tagged
|
||||
end
|
||||
end
|
||||
for _, descendant in hudRoot:GetDescendants() do
|
||||
if descendant:IsA("GuiObject") and descendant:GetAttribute(COLLAPSIBLE_ATTR) ~= nil then
|
||||
return descendant
|
||||
end
|
||||
end
|
||||
local byNameMatch = hudRoot:FindFirstChild(COLLAPSIBLE_NAME, true)
|
||||
if byNameMatch and byNameMatch:IsA("GuiObject") then
|
||||
return byNameMatch
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local toggles: { [Instance]: () -> () } = {}
|
||||
local function wireToggle(button: Instance)
|
||||
if toggles[button] or not isToggle(button) or not button:IsDescendantOf(playerGui) then
|
||||
return
|
||||
end
|
||||
local target = findCollapsible(button)
|
||||
if not target or not target:IsA("GuiObject") then
|
||||
return -- minimal HUDs with a button but no container: skip silently
|
||||
end
|
||||
local container: GuiObject = target
|
||||
local guiButton: GuiButton = button :: GuiButton
|
||||
|
||||
local function apply()
|
||||
-- Rotate the same glyph 180° to flip the chevron — no second glyph / asset.
|
||||
guiButton.Rotation = if container.Visible then 180 else 0
|
||||
end
|
||||
apply()
|
||||
local connection = guiButton.Activated:Connect(function()
|
||||
container.Visible = not container.Visible
|
||||
apply()
|
||||
end)
|
||||
toggles[button] = function()
|
||||
connection:Disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
for _, instance in playerGui:GetDescendants() do
|
||||
if isBar(instance) then
|
||||
tryBind(instance)
|
||||
elseif isToggle(instance) then
|
||||
wireToggle(instance)
|
||||
end
|
||||
end
|
||||
playerGui.DescendantAdded:Connect(function(instance)
|
||||
if isBar(instance) then
|
||||
tryBind(instance)
|
||||
elseif isToggle(instance) then
|
||||
wireToggle(instance)
|
||||
end
|
||||
end)
|
||||
playerGui.DescendantRemoving:Connect(function(instance)
|
||||
@@ -157,6 +304,11 @@ function Hud.start(_options: { [string]: any }?)
|
||||
end
|
||||
bound[instance] = nil
|
||||
renderers[instance] = nil
|
||||
local untoggle = toggles[instance]
|
||||
if untoggle then
|
||||
untoggle()
|
||||
toggles[instance] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
for _, bar in CollectionService:GetTagged(BAR_TAG) do
|
||||
@@ -164,6 +316,11 @@ function Hud.start(_options: { [string]: any }?)
|
||||
end
|
||||
CollectionService:GetInstanceAddedSignal(BAR_TAG):Connect(tryBind)
|
||||
|
||||
for _, button in CollectionService:GetTagged(TOGGLE_TAG) do
|
||||
wireToggle(button)
|
||||
end
|
||||
CollectionService:GetInstanceAddedSignal(TOGGLE_TAG):Connect(wireToggle)
|
||||
|
||||
-- Re-render bars live when the owner edits the Studio config (WarnAt / Max / colors).
|
||||
local function rerenderAll()
|
||||
byName = StatConfig.resolve().byName
|
||||
|
||||
@@ -32,16 +32,45 @@ function HudFallback.build(playerGui: Instance, stats: { any })
|
||||
local screen = Instance.new("ScreenGui")
|
||||
screen.Name = HUD_NAME
|
||||
screen.ResetOnSpawn = false
|
||||
screen.IgnoreGuiInset = true
|
||||
-- Respect the ~36px Roblox topbar inset so bars never render under the CoreGui menu.
|
||||
screen.IgnoreGuiInset = false
|
||||
screen.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
|
||||
|
||||
-- A dark translucent background panel so the fallback reads cleanly, matching the
|
||||
-- authored template's look. Auto-sizes to its bars; the owner never edits this file.
|
||||
local panel = Instance.new("Frame")
|
||||
panel.Name = "Panel"
|
||||
panel.Position = UDim2.fromOffset(16, 12)
|
||||
panel.Size = UDim2.fromOffset(248, 0)
|
||||
panel.AutomaticSize = Enum.AutomaticSize.Y
|
||||
panel.BackgroundColor3 = Color3.fromRGB(20, 23, 30)
|
||||
panel.BackgroundTransparency = 0.25
|
||||
panel.BorderSizePixel = 0
|
||||
panel.Parent = screen
|
||||
|
||||
local corner = Instance.new("UICorner")
|
||||
corner.CornerRadius = UDim.new(0, 10)
|
||||
corner.Parent = panel
|
||||
|
||||
local stroke = Instance.new("UIStroke")
|
||||
stroke.Color = Color3.fromRGB(210, 220, 245)
|
||||
stroke.Thickness = 1
|
||||
stroke.Transparency = 0.85
|
||||
stroke.Parent = panel
|
||||
|
||||
local padding = Instance.new("UIPadding")
|
||||
padding.PaddingTop = UDim.new(0, 10)
|
||||
padding.PaddingBottom = UDim.new(0, 10)
|
||||
padding.PaddingLeft = UDim.new(0, 12)
|
||||
padding.PaddingRight = UDim.new(0, 12)
|
||||
padding.Parent = panel
|
||||
|
||||
local container = Instance.new("Frame")
|
||||
container.Name = "Bars"
|
||||
container.Position = UDim2.fromOffset(18, 18)
|
||||
container.Size = UDim2.fromOffset(220, 0)
|
||||
container.Size = UDim2.fromScale(1, 0)
|
||||
container.AutomaticSize = Enum.AutomaticSize.Y
|
||||
container.BackgroundTransparency = 1
|
||||
container.Parent = screen
|
||||
container.Parent = panel
|
||||
|
||||
local layout = Instance.new("UIListLayout")
|
||||
layout.Padding = UDim.new(0, 6)
|
||||
@@ -75,14 +104,31 @@ function HudFallback.build(playerGui: Instance, stats: { any })
|
||||
local label = Instance.new("TextLabel")
|
||||
label.Name = "Label"
|
||||
label.BackgroundTransparency = 1
|
||||
label.Size = UDim2.fromScale(1, 1)
|
||||
label.Size = UDim2.new(1, -64, 1, 0)
|
||||
label.Position = UDim2.fromOffset(8, 0)
|
||||
label.Text = stat.name
|
||||
label.TextColor3 = Color3.fromRGB(245, 245, 245)
|
||||
label.TextSize = 12
|
||||
label.Font = Enum.Font.GothamMedium
|
||||
label.TextXAlignment = Enum.TextXAlignment.Left
|
||||
label.ZIndex = 2
|
||||
label.Parent = bar
|
||||
|
||||
-- Empty `Value` label the binder fills with the formatted number (optional but
|
||||
-- cheap; keeps the fallback consistent with the authored template).
|
||||
local valueLabel = Instance.new("TextLabel")
|
||||
valueLabel.Name = "Value"
|
||||
valueLabel.BackgroundTransparency = 1
|
||||
valueLabel.Size = UDim2.new(0, 52, 1, 0)
|
||||
valueLabel.Position = UDim2.new(1, -58, 0, 0)
|
||||
valueLabel.Text = ""
|
||||
valueLabel.TextColor3 = Color3.fromRGB(245, 245, 245)
|
||||
valueLabel.TextSize = 12
|
||||
valueLabel.Font = Enum.Font.GothamMedium
|
||||
valueLabel.TextXAlignment = Enum.TextXAlignment.Right
|
||||
valueLabel.ZIndex = 2
|
||||
valueLabel.Parent = bar
|
||||
|
||||
CollectionService:AddTag(bar, BAR_TAG)
|
||||
-- Parent last, fully built, so the binder sees Stat + Fill when it fires.
|
||||
bar.Parent = container
|
||||
|
||||
@@ -31,4 +31,16 @@ function Assets.get(category: string, key: string): string
|
||||
return id
|
||||
end
|
||||
|
||||
-- Like `get` but SILENT: returns "" for a missing/empty asset without warning.
|
||||
-- Use it on hot paths (e.g. the HUD resolving an optional per-stat icon every render)
|
||||
-- where a missing id is an expected, non-error case.
|
||||
function Assets.tryGet(category: string, key: string): string
|
||||
local cat = store[category]
|
||||
local id = cat and cat[key]
|
||||
if typeof(id) == "string" then
|
||||
return id
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
return Assets
|
||||
|
||||
@@ -29,6 +29,8 @@ local STUDIO_ATTR_MAP = {
|
||||
WarnAt = "warnAt",
|
||||
Invert = "invert",
|
||||
Display = "display",
|
||||
Icon = "icon",
|
||||
ValueFormat = "valueFormat",
|
||||
}
|
||||
|
||||
local function applyTable(target: { [string]: any }, override: { [string]: any }?)
|
||||
@@ -65,6 +67,8 @@ export type ResolvedStat = {
|
||||
invert: boolean,
|
||||
warnAt: number,
|
||||
display: boolean,
|
||||
icon: string, -- HUD icon asset id, "" when none (engine ships "" — content-free)
|
||||
valueFormat: string, -- HUD numeric readout: "fraction" | "percent" | "value" | "none"
|
||||
}
|
||||
|
||||
-- Returns the merged stat models, both as an ordered array and a name lookup.
|
||||
@@ -84,6 +88,8 @@ function StatConfig.resolve(): { stats: { ResolvedStat }, byName: { [string]: Re
|
||||
invert = def.invert,
|
||||
warnAt = def.warnAt,
|
||||
display = def.display,
|
||||
icon = def.icon or "",
|
||||
valueFormat = def.valueFormat or "fraction",
|
||||
}
|
||||
if section then
|
||||
applyTable(resolved, section[def.name])
|
||||
|
||||
@@ -23,6 +23,8 @@ export type StatDef = {
|
||||
warnAt: number, -- warn when the displayed bar drops below this percent (0-100)
|
||||
display: boolean, -- show in the HUD by default
|
||||
attribute: string, -- Player attribute name (defaults to `name`)
|
||||
icon: string?, -- HUD icon asset id; ALWAYS "" here (content-free) — owners supply art
|
||||
valueFormat: string?, -- HUD numeric readout: "fraction" | "percent" | "value" | "none"
|
||||
}
|
||||
|
||||
local StatDefs = {}
|
||||
@@ -91,6 +93,8 @@ function StatDefs.install()
|
||||
invert = def.invert,
|
||||
warnAt = def.warnAt,
|
||||
display = def.display,
|
||||
icon = def.icon or "", -- content-free default; owners supply art via config/Assets
|
||||
valueFormat = def.valueFormat or "fraction",
|
||||
}
|
||||
Registries.Stats.register({
|
||||
name = def.name,
|
||||
@@ -101,6 +105,8 @@ function StatDefs.install()
|
||||
warnAt = def.warnAt,
|
||||
display = def.display,
|
||||
attribute = def.name,
|
||||
icon = def.icon or "",
|
||||
valueFormat = def.valueFormat or "fraction",
|
||||
})
|
||||
end
|
||||
Config.defineSection("SurvivalStats", section)
|
||||
|
||||
Reference in New Issue
Block a user