From 2cb39109e9a2707ed209cfe8e1faf8e8ecd99ab5 Mon Sep 17 00:00:00 2001 From: Samuel Lison Date: Mon, 22 Jun 2026 10:05:31 +1000 Subject: [PATCH 1/2] fix(hud): read stats by their backing attribute, not the stat name (#20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A stat's value lives on the player under its `attribute` (which defaults to the name but a game can override via Config.override("SurvivalStats", {Stat={attribute=...}})). The client read the stat NAME instead, so any custom attribute override silently froze the bar. Fixed in all three spots that hardcoded the name: - src/client/Hud.luau — the bar binder now resolves def.attribute and reads + listens on it (was GetAttribute(statName) / GetAttributeChangedSignal(statName)). - src/systems/SurvivalConsequences.luau — the Health<->Humanoid sync writes the resolved Health attribute (was hardcoded "Health"). - src/client/MovementFeedback.luau — the energy vignette/breathing reads the resolved Energy attribute (was hardcoded "Energy"). Default behaviour (attribute = name) is unchanged. Verified in Studio: with Health.attribute="HP" / Energy.attribute="EP", resolve() yields HP/EP and the fixed code reads/writes there; un-overridden stats still default to the name. Gate green. Closes #20 Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 8 ++++++++ src/client/Hud.luau | 9 +++++++-- src/client/MovementFeedback.luau | 7 ++++++- src/systems/SurvivalConsequences.luau | 6 +++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6a5f04..2070614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ is promoted to the new version and `main` is tagged `vX.Y.Z`. ## Unreleased +### Fixed +- **Custom stat `attribute` overrides now work end to end** (#20) — the HUD bound stat bars by the + stat *name*, but the engine stores each value under the stat's backing `attribute` (overridable + via `Config.override("SurvivalStats", { Stat = { attribute = "…" } })`), so an override silently + froze the bar. The binder now reads + listens on the resolved `attribute`; the same hardcoded-name + slip in the Health↔Humanoid sync and the energy (vignette/breathing) feedback is fixed too. + Default behaviour (attribute defaults to the name) is unchanged. + ## 0.2.0 — 2026-06-20 ### Added diff --git a/src/client/Hud.luau b/src/client/Hud.luau index 1c77502..f8023c3 100644 --- a/src/client/Hud.luau +++ b/src/client/Hud.luau @@ -156,6 +156,11 @@ function Hud.start(_options: { [string]: any }?) warn(`[SurvivorCore HUD] bar '{bar:GetFullName()}' is missing a 'Stat' attribute`) return end + -- The bar's `Stat` attribute names the stat; the value lives on the player under the stat's + -- backing attribute (`def.attribute`, which defaults to the name). Read + listen on THAT, + -- so a custom `attribute` override (set in config before start, never changes at runtime) + -- stays in sync with the HUD instead of silently reading a name nobody writes to. + local attrName = (byName[statName] and byName[statName].attribute) or statName local fill = bar:FindFirstChild("Fill") if not fill or not fill:IsA("GuiObject") then warn(`[SurvivorCore HUD] bar '{bar:GetFullName()}' is missing a 'Fill' GuiObject child`) @@ -191,7 +196,7 @@ function Hud.start(_options: { [string]: any }?) local normalColor = bar:GetAttribute("FillColor") or authoredColor local warnColor = bar:GetAttribute("WarnColor") or DEFAULT_WARN_COLOR - local value = localPlayer:GetAttribute(statName) + local value = localPlayer:GetAttribute(attrName) local ratio = 0 if typeof(value) == "number" and max > 0 then ratio = math.clamp(value / max, 0, 1) @@ -234,7 +239,7 @@ function Hud.start(_options: { [string]: any }?) end end - local connection = localPlayer:GetAttributeChangedSignal(statName):Connect(render) + local connection = localPlayer:GetAttributeChangedSignal(attrName):Connect(render) -- also re-render when the bar's own Icon is set at runtime (lets the game assign art live) local iconConn = bar:GetAttributeChangedSignal("Icon"):Connect(render) renderers[bar] = render diff --git a/src/client/MovementFeedback.luau b/src/client/MovementFeedback.luau index 4c07bf8..cb0d648 100644 --- a/src/client/MovementFeedback.luau +++ b/src/client/MovementFeedback.luau @@ -22,6 +22,7 @@ assert(RunService:IsClient(), "SurvivorCore.MovementFeedback is client-only — local MovementConfig = require(script.Parent.Parent.shared.MovementConfig) local Remotes = require(script.Parent.Parent.shared.Remotes) +local StatConfig = require(script.Parent.Parent.stats.StatConfig) local MovementFeedback = {} @@ -55,6 +56,10 @@ function MovementFeedback.start(_options: { [string]: any }?) local player = Players.LocalPlayer local cfg = MovementConfig.get() + -- Read Energy from its backing attribute (defaults to "Energy"), respecting a custom override. + local energyDef = StatConfig.resolve().byName.Energy + local energyAttr = (energyDef and energyDef.attribute) or "Energy" + -- --- Sprint input → server ------------------------------------------------- local sprintRemote = Remotes.event("SprintIntent") local sprintHeld = false @@ -109,7 +114,7 @@ function MovementFeedback.start(_options: { [string]: any }?) local heartbeat = makeLoopedSound("SurvivorCoreHeartbeat", cfg.Assets.Heartbeat) RunService.RenderStepped:Connect(function(dt) - local energy = player:GetAttribute("Energy") + local energy = player:GetAttribute(energyAttr) if typeof(energy) ~= "number" then energy = 0 end diff --git a/src/systems/SurvivalConsequences.luau b/src/systems/SurvivalConsequences.luau index 8c601e2..032a24a 100644 --- a/src/systems/SurvivalConsequences.luau +++ b/src/systems/SurvivalConsequences.luau @@ -57,7 +57,11 @@ local function isAtMax(player: Player, name: string): boolean end local function syncHealth(player: Player, humanoid: Humanoid) - player:SetAttribute("Health", math.max(0, math.round(humanoid.Health))) + -- Write to the Health stat's backing attribute (defaults to "Health"), so a custom + -- `attribute` override still lands where the HUD reads it. + local def = SurvivalStats.getDefinition("Health") + local attr = (def and def.attribute) or "Health" + player:SetAttribute(attr, math.max(0, math.round(humanoid.Health))) end local function setupCharacter(player: Player, character: Model) From 9a1052396596f4b155d5133d8775f2a411699b33 Mon Sep 17 00:00:00 2001 From: Samuel Lison Date: Mon, 22 Jun 2026 10:25:46 +1000 Subject: [PATCH 2/2] =?UTF-8?q?chore(release):=20v0.2.1=20=E2=80=94=20prom?= =?UTF-8?q?ote=20CHANGELOG=20(#20=20fix),=20bump=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote the Unreleased #20 fix to 0.2.1 and bump SurvivorCore.VERSION + wally.toml. Promotes WITHOUT re-adding an empty `## Unreleased` section, so the released changelog on `main` doesn't carry a confusing dangling heading (a fresh `## Unreleased` reappears when the next change adds an entry). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 2 +- src/init.luau | 2 +- wally.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2070614..1a0fe54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to SurvivorCore are recorded here. The format follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). At release time, `## Unreleased` is promoted to the new version and `main` is tagged `vX.Y.Z`. -## Unreleased +## 0.2.1 — 2026-06-22 ### Fixed - **Custom stat `attribute` overrides now work end to end** (#20) — the HUD bound stat bars by the diff --git a/src/init.luau b/src/init.luau index cade5a2..971171e 100644 --- a/src/init.luau +++ b/src/init.luau @@ -35,7 +35,7 @@ require(script.shared.ConsequenceConfig) local SurvivorCore = {} -SurvivorCore.VERSION = "0.2.0" +SurvivorCore.VERSION = "0.2.1" -- Foundation SurvivorCore.Config = Config diff --git a/wally.toml b/wally.toml index 84cbfec..d565ee7 100644 --- a/wally.toml +++ b/wally.toml @@ -1,7 +1,7 @@ [package] name = "temujincalidius/survivorcore" description = "Batteries-included, creator-extensible survival game framework for Roblox." -version = "0.2.0" +version = "0.2.1" license = "MIT" authors = ["Samuel Lison"] registry = "https://github.com/UpliftGames/wally-index"