Files
SurvivorCore/src/components/Mob.luau
T
Samuel LisonandClaude Opus 4.8 29bd20a93f feat(builder): schema-driven Build page for world objects (#11)
Select a Part or Model in Studio, answer "what is this object?", fill a form —
it becomes a gatherable node, a mob or a quest giver. Closes the gap between
authoring a def and setting up a world object, which until now meant knowing to
tag a part and hand-typing PascalCase attributes in the property panel.

Engine — components can declare an attribute SCHEMA:
- src/components/Schema.luau (new): AttributeSpec/Display/ComponentSchema types,
  normalize/defaults/get/list, and the schemas for Gatherable, Mob, QuestGiver.
  Dependency-free ON PURPOSE: the plugin requires it live at edit time, and the
  component modules themselves can't be required there (Harvesting asserts
  IsServer; Remotes creates instances in ReplicatedStorage).
- Components.define now accepts EITHER the legacy `attr = default` map or a
  schema array, normalizing both to one ordered spec list; bind() reads the
  derived default map, so binding is byte-identical. Legacy maps are sorted, as
  `pairs` order is arbitrary and would make a UI jitter. New getSchema/
  listSchemas. The three shipped components pull name/tag/display/attributes
  from the schema; their onSetup bodies are untouched (defaults verified
  identical, all 23 attributes).

Plugin — the Build page:
- Field.luau (new): coerce/format/equalsDefault, lifted from ConfigAdmin (which
  now delegates), shared by every schema-driven editor.
- FieldRow.luau (new): the shared [○/●] label … control + help row, including a
  ⌄ picker that cycles authored ids for fields declaring `ref`.
- BuildAdmin.luau (new): live schema read with three distinct empty states,
  selection/eligibility/identify, deltas-only attribute writes, applyType
  (tag + clear any other component) and clear.
- BuildAdminUi.luau (new): chooser cards, grouped form, multi-select apply,
  stale-bind-marker warning, SelectionChanged-driven refresh.
- init.server.luau: record()-wrapped buildActions + the page.

Docs: docs/admin-plugin.md Build section + a 60-second walkthrough,
docs/extending.md schema guide, a CONTRIBUTING rule that new creator components
declare one, CHANGELOG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 17:14:52 +10:00

37 lines
1.7 KiB
Luau
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
--[[
Mob the creature component. A creator builds (or imports) any rigged Model with a Humanoid +
PrimaryPart, tags it "Mob", and sets `MobType = "<id>"` to inherit health/speed/ranges/faction
from a `SurvivorCore.Mobs` def (authored in code or no-code via the admin plugin). Per-instance
attributes (Faction, Health, AggroRange, ) override the def for that one mob.
On setup the node is handed to the server `Mobs` runtime (`Mobs.adopt`), which resolves its
effective stats, sets the Humanoid health, and starts the AI FSM (idle/wander/chase/attack/flee).
Combat damages it like any Humanoid; per-type juice (death fade, spawn cry) attaches via
`SurvivorCore.Mobs.onReaction` no core edits. This is the Builder-UI-drivable creature path
(#11/#14): the attribute set below is the schema the admin plugin's Mobs editor writes.
]]
local Components = require(script.Parent)
local Schema = require(script.Parent.Schema)
local Mobs = require(script.Parent.Parent.systems.Mobs)
-- Attributes + their creator-facing help text live in the schema (the Studio plugin renders this
-- component's setup form from it).
local SCHEMA = Schema.COMPONENTS.Mob
return Components.define({
name = SCHEMA.name,
tag = SCHEMA.tag,
display = SCHEMA.display,
attributes = SCHEMA.attributes,
onSetup = function(instance, _values)
-- The server Mobs runtime reads the instance attributes directly (one merge path:
-- override attr registry def "Mobs" Config), so nothing to stash here — just adopt it.
if instance:IsA("Model") then
Mobs.adopt(instance)
else
warn(`[Mob] '{instance:GetFullName()}' must be a Model (Humanoid + PrimaryPart) to be a mob`)
end
end,
})