mirror of
https://github.com/TemujinCalidius/SurvivorCore.git
synced 2026-08-14 00:58:01 +00:00
feat(hud): ship free default stat icons (visible in Studio Edit)
Owner relaxed the asset side of the content-free rule — assets in this repo are free to use — so the engine now ships its generated HUD icons as defaults instead of leaving blank slots. Two wins: the HUD is iconed out of the box, and the icons render in Studio's EDIT view (no Play needed), so you can author/tune the HUD and see the real thing — or edit it straight from the Explorer. - StatDefs.luau: each of the 7 stats carries an `icon` default. Putting it here (not as a per-bar HUD attribute) keeps the admin-panel/config override working — a per-bar attribute would shadow it. resolveIcon: per-bar attr > config/def.icon (now the shipped id) > Assets > "". - assets/hud/SurvivalHud.model.json: bake each bar's child `Icon` ImageLabel (Image + Visible=true) so it shows in edit mode; the Credits counter (no StatDefs entry) also gets a per-bar `Icon` attribute to drive its runtime resolution. - Retire demo/client/HudIcons.client.luau (+ its demo.project.json mount): the runtime icon-assigning script is now redundant. - Relax the docs/PR-template "content-free" wording to "design-free": free default ART may ship (overridable per stat); game-specific DESIGN (items/recipes/lore) still never ships. Updated design-language, survival-stats, CONTRIBUTING. - CHANGELOG: record default icons + add the missing admin-plugin entry; note CI now builds all three Rojo targets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2e7bd69c2c
commit
69bbf12be9
@@ -13,6 +13,6 @@
|
||||
## Checklist
|
||||
|
||||
- [ ] Added an entry to **`CHANGELOG.md`** under `## Unreleased` (or applied the `skip-changelog` label if no entry is warranted — e.g. a CI-only or trivial docs change)
|
||||
- [ ] CI is green — `stylua --check`, `selene`, `luau-lsp analyze`, and **both** `rojo build`s pass
|
||||
- [ ] CI is green — `stylua --check`, `selene`, `luau-lsp analyze`, and **all three** `rojo build`s (engine, demo, plugin) pass
|
||||
- [ ] No secrets or personal data added (this is a public repo)
|
||||
- [ ] **No hardcoded asset IDs** — content comes via `register()` / components / `Assets`, and the core stays content-free
|
||||
- [ ] **No game-specific design content** — items / recipes / lore / world strings enter via `register()` / components / `Assets`, not the engine (free *default* art, e.g. HUD icons, is fine)
|
||||
|
||||
+18
-6
@@ -13,21 +13,33 @@ is promoted to the new version and `main` is tagged `vX.Y.Z`.
|
||||
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. 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). Afflictions
|
||||
to the full roster, with per-bar **numeric readouts** (`99/100` / `%`, configurable) and per-stat
|
||||
**icons** — the engine ships a free default icon set (baked into `StatDefs` + the HUD template),
|
||||
so the HUD is iconed out of the box and the icons even show in Studio's **Edit** view, no Play
|
||||
needed. Afflictions
|
||||
(Hunger/Thirst/Fatigue/Poison) read as empty-when-safe and fill up as they worsen (`dangerHigh`),
|
||||
and the header carries a **credits** readout (a bar-less `Counter` bound to a Player attribute).
|
||||
HUD icons update **live** — a bar/counter shows its art the moment its `Icon` attribute is set, so
|
||||
a game can assign icons at runtime (the demo ships an example flat icon set; the engine ships none). Ships with seven default stats (health/energy/hunger/thirst/fatigue/blood/
|
||||
HUD icons resolve per bar/counter and update **live** — a game can swap art at runtime by setting
|
||||
the `Icon` attribute, or override the shipped defaults per stat (config / admin plugin / `Assets`).
|
||||
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
|
||||
on `start()`, and a runtime fallback guarantees a HUD always appears. Adds the engine's first
|
||||
client layer (`SurvivorCore.startClient()`). See [docs/survival-stats.md](docs/survival-stats.md).
|
||||
- **Survival Stats admin plugin** (#11, first slice) — a Studio dock widget to tune the survival
|
||||
stats from a validated form instead of hand-editing Attributes. It writes **deltas only** (an
|
||||
attribute only when a field differs from the engine default; removed on reset / edit-back), so
|
||||
owner tuning survives engine updates while untouched fields keep following improvable defaults,
|
||||
and a hard guardrail makes the engine-owned `Invert`/`DangerHigh` semantics impossible to write.
|
||||
Built as a separate `plugin.project.json` Rojo target (install via
|
||||
`rojo build plugin.project.json --plugin …`); CI lints and builds it too.
|
||||
See [docs/admin-plugin.md](docs/admin-plugin.md).
|
||||
- **Continuous integration** (`.github/workflows/ci.yml`) — every push to `main`/`dev` and
|
||||
every PR runs `stylua --check`, `selene`, `luau-lsp analyze` (against a Rojo sourcemap +
|
||||
Roblox type defs), then builds **both** `default.project.json` (the drop-in engine model)
|
||||
and `demo.project.json` (the runnable demo place) to prove they compile.
|
||||
Roblox type defs), then builds **all three** Rojo targets — `default.project.json` (the drop-in
|
||||
engine model), `demo.project.json` (the runnable demo place), and `plugin.project.json` (the
|
||||
admin plugin) — to prove they compile.
|
||||
- **Changelog enforcement** (`.github/workflows/changelog.yml`) — PRs must update
|
||||
`CHANGELOG.md` unless they carry the `skip-changelog` label.
|
||||
- **Contributor guide** (`CONTRIBUTING.md`) — prerequisites, local dev setup, code style,
|
||||
|
||||
+9
-6
@@ -69,11 +69,14 @@ components, hooks, and the foundation. See [Architecture Overview](#architecture
|
||||
- **Formatting & linting are enforced by CI.** Before you push, run the same checks CI does
|
||||
(see [Making a Pull Request](#making-a-pull-request)). `stylua` owns formatting — don't
|
||||
hand-format around it.
|
||||
- **Never hardcode asset IDs in `.luau`.** Asset IDs are content. Register them through
|
||||
`Assets` (`Assets.register("Sounds", "Harvest", "rbxassetid://…")`) and read them back, with
|
||||
the empty-string fallback convention. The engine must contain **zero** concrete asset IDs.
|
||||
- **Keep the core content-free.** No concrete items, recipes, lore, world strings, or
|
||||
instance-name string matches (`name == "campfire"`) in the engine. Content enters two ways:
|
||||
- **Default art is fine; keep it swappable.** Assets in this repo are free to use, so the engine
|
||||
may ship free *default* art IDs (e.g. the HUD stat icons in `StatDefs`) — provided they stay
|
||||
overridable (config / admin plugin / `Assets`) and never get buried in logic. For dynamic or
|
||||
owner-supplied art, still route through the `Assets` registry
|
||||
(`Assets.register("Sounds", "Harvest", "rbxassetid://…")`) with the empty-string fallback.
|
||||
- **Keep the core free of game-specific *design*.** No concrete items, recipes, lore, world
|
||||
strings, or instance-name string matches (`name == "campfire"`) in the engine — free default
|
||||
*art* is the one exception (above). Content enters two ways:
|
||||
- **Registries** — developers call `register()` from code (`Items`, `Recipes`, `Stats`,
|
||||
`Mobs`, …).
|
||||
- **Components** — creators tag their own objects and set Attributes (`Gatherable`, and the
|
||||
@@ -116,7 +119,7 @@ published (which auto-posts to Discussions → Announcements).
|
||||
# git checkout -b docs/my-fix main # documentation only
|
||||
```
|
||||
3. **Implement your change.** Write clear, typed Luau. Add comments where the "why" isn't
|
||||
obvious. Keep the engine content-free.
|
||||
obvious. Keep the engine free of game-specific design (free default art is fine).
|
||||
4. **Test locally.** Play-test in Studio, then run the same checks CI does:
|
||||
```bash
|
||||
stylua --check src demo assets plugin
|
||||
|
||||
@@ -167,7 +167,8 @@
|
||||
"BorderSizePixel": 0
|
||||
},
|
||||
"attributes": {
|
||||
"Counter": "Credits"
|
||||
"Counter": "Credits",
|
||||
"Icon": "rbxassetid://137051934393677"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
@@ -201,9 +202,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://137051934393677",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"LayoutOrder": 1
|
||||
}
|
||||
},
|
||||
@@ -449,9 +450,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://88514622686548",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
@@ -668,9 +669,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://129856164091801",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
@@ -925,9 +926,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://138699077112926",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
@@ -1144,9 +1145,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://124374881225649",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
@@ -1363,9 +1364,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://102789813187589",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
@@ -1582,9 +1583,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://139788071928396",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
@@ -1801,9 +1802,9 @@
|
||||
]
|
||||
},
|
||||
"BackgroundTransparency": 1,
|
||||
"Image": "",
|
||||
"Image": "rbxassetid://86406240258610",
|
||||
"ScaleType": "Fit",
|
||||
"Visible": false,
|
||||
"Visible": true,
|
||||
"ZIndex": 2
|
||||
}
|
||||
},
|
||||
|
||||
+1
-2
@@ -17,8 +17,7 @@
|
||||
"$className": "StarterPlayer",
|
||||
"StarterPlayerScripts": {
|
||||
"$className": "StarterPlayerScripts",
|
||||
"HudLoader": { "$path": "assets/client/HudLoader.client.luau" },
|
||||
"HudIcons": { "$path": "demo/client/HudIcons.client.luau" }
|
||||
"HudLoader": { "$path": "assets/client/HudLoader.client.luau" }
|
||||
}
|
||||
},
|
||||
"Workspace": {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
--!nonstrict
|
||||
--[[
|
||||
DEMO ONLY — assigns the demo's generated survival-stat icons to the HUD.
|
||||
|
||||
The engine ships ZERO asset ids (content-free); a real game does this for its own art.
|
||||
Setting a bar/counter's `Icon` attribute makes the HUD binder render it (the binder
|
||||
re-renders when `Icon` changes, so timing doesn't matter). These are flat, game-agnostic
|
||||
icons generated for the demo via 3D AI Studio (Gemini 3 Pro).
|
||||
]]
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
|
||||
local ICONS: { [string]: string } = {
|
||||
Health = "rbxassetid://88514622686548",
|
||||
Energy = "rbxassetid://129856164091801",
|
||||
Hunger = "rbxassetid://138699077112926",
|
||||
Thirst = "rbxassetid://124374881225649",
|
||||
Fatigue = "rbxassetid://102789813187589",
|
||||
Blood = "rbxassetid://139788071928396",
|
||||
Poison = "rbxassetid://86406240258610",
|
||||
Credits = "rbxassetid://137051934393677",
|
||||
}
|
||||
|
||||
local function apply(hud: Instance)
|
||||
for _, descendant in hud:GetDescendants() do
|
||||
local key = descendant:GetAttribute("Stat") or descendant:GetAttribute("Counter")
|
||||
if typeof(key) == "string" and ICONS[key] and ICONS[key] ~= "" then
|
||||
descendant:SetAttribute("Icon", ICONS[key])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local hud = playerGui:WaitForChild("SurvivalHud", 10)
|
||||
if hud then
|
||||
apply(hud)
|
||||
end
|
||||
playerGui.ChildAdded:Connect(function(child)
|
||||
if child.Name == "SurvivalHud" then
|
||||
task.wait(0.2)
|
||||
apply(child)
|
||||
end
|
||||
end)
|
||||
@@ -8,8 +8,11 @@ re-skin freely; these are the engine's clean defaults and the rules any shipped
|
||||
|
||||
## Principles
|
||||
|
||||
- **Content-free.** The engine ships **zero** `rbxassetid://`. Art is owner-supplied or
|
||||
generated into the `Assets` registry; defaults are plain colors + text.
|
||||
- **Free, swappable defaults.** Assets in this repo are free to use, so the engine ships free
|
||||
**default** art (e.g. the HUD stat icons) — every piece overridable per stat (config / admin
|
||||
plugin) or via the `Assets` registry. No look is hard-coded; where no art resolves, the fallback
|
||||
is plain colors + text. (Game-specific *design* — items, recipes, lore — still never ships in the
|
||||
engine; see [CONTRIBUTING](../CONTRIBUTING.md).)
|
||||
- **Owner-editable.** UI is real, restyleable Instances driven by attributes — never hard-coded
|
||||
layouts the owner can't change.
|
||||
- **Tasteful, not flashy.** Clean, modern, readable. Flat surfaces, soft translucency, restraint.
|
||||
|
||||
@@ -103,7 +103,8 @@ set only `Stat`): `Max`, `Invert`, `WarnAt`, `FillColor`, `WarnColor`, `FillAxis
|
||||
|
||||
**Icons** resolve from the per-bar `Icon` attribute → the stat's `icon` config → the `Assets`
|
||||
registry category **`StatIcons`** (key = stat name) — e.g. `SurvivorCore.Assets.register("StatIcons",
|
||||
"Hunger", "rbxassetid://…")`. The engine ships none, so slots stay blank until you supply art.
|
||||
"Hunger", "rbxassetid://…")`. The engine now ships **default** icons for its built-in stats (as
|
||||
each stat's `icon` default), so bars are iconed out of the box; the chain above replaces them.
|
||||
|
||||
**Collapse** is wired generically: a `GuiButton` with a **`HudToggle`** attribute (its value names
|
||||
the container, e.g. `"Collapsible"`) toggles a container marked **`HudCollapsible`**. Both are also
|
||||
@@ -115,7 +116,8 @@ To re-skin: edit the `Fill` and surrounding elements however you like. (Bars tag
|
||||
|
||||
## Providing your own HUD art
|
||||
|
||||
The engine ships **no** icons (content-free). Supply your own — first match wins:
|
||||
The engine ships **default** icons for its built-in stats, so the HUD is iconed out of the box (and
|
||||
they show in Studio's Edit view, no Play needed). To use your own art, set any of these — first match wins:
|
||||
|
||||
1. set a bar's **`Icon`** attribute (or the credits/counter element's) to an asset id — **live**:
|
||||
the HUD renders it the instant it's set, so a game can assign art at runtime;
|
||||
|
||||
@@ -72,7 +72,7 @@ export type ResolvedStat = {
|
||||
invert: boolean,
|
||||
warnAt: number,
|
||||
display: boolean,
|
||||
icon: string, -- HUD icon asset id, "" when none (engine ships "" — content-free)
|
||||
icon: string, -- HUD icon asset id, "" when none (engine ships free defaults; overridable)
|
||||
valueFormat: string, -- HUD numeric readout: "fraction" | "percent" | "value" | "none"
|
||||
}
|
||||
|
||||
|
||||
+10
-3
@@ -25,7 +25,7 @@ export type StatDef = {
|
||||
warnAt: number, -- warn within this percent of the dangerous end (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
|
||||
icon: string?, -- HUD icon asset id; engine ships free default icons (overridable per stat)
|
||||
valueFormat: string?, -- HUD numeric readout: "fraction" | "percent" | "value" | "none"
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ local function perMinutes(minutes: number): number
|
||||
return 100 / (minutes * 60)
|
||||
end
|
||||
|
||||
-- The shipped defaults. Numbers ported from The Counter Earth; content-free.
|
||||
-- The shipped defaults. Numbers + default icons ported from The Counter Earth.
|
||||
-- Hunger/Thirst/Fatigue/Poison start at 0 and rise toward 100 = bad (`dangerHigh = true`,
|
||||
-- so their bars fill up as they worsen). Health/Energy/Blood are resources (`dangerHigh =
|
||||
-- false`: 0 = bad). Health/Energy are display-only here (drain/regen + consequences = #5/#6).
|
||||
@@ -50,6 +50,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://88514622686548",
|
||||
},
|
||||
{
|
||||
name = "Energy",
|
||||
@@ -60,6 +61,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://129856164091801",
|
||||
},
|
||||
{
|
||||
name = "Hunger",
|
||||
@@ -70,6 +72,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://138699077112926",
|
||||
},
|
||||
{
|
||||
name = "Thirst",
|
||||
@@ -80,6 +83,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://124374881225649",
|
||||
},
|
||||
{
|
||||
name = "Fatigue",
|
||||
@@ -90,6 +94,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://102789813187589",
|
||||
},
|
||||
{
|
||||
name = "Poison",
|
||||
@@ -100,6 +105,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://86406240258610",
|
||||
},
|
||||
{
|
||||
name = "Blood",
|
||||
@@ -110,6 +116,7 @@ StatDefs.DEFAULTS = {
|
||||
invert = false,
|
||||
warnAt = 25,
|
||||
display = true,
|
||||
icon = "rbxassetid://139788071928396",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -135,7 +142,7 @@ 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
|
||||
icon = def.icon or "", -- engine ships a default icon; owners override via config/Assets
|
||||
valueFormat = def.valueFormat or "fraction",
|
||||
}
|
||||
Registries.Stats.register({
|
||||
|
||||
Reference in New Issue
Block a user