3.7 KiB
Harvesting (gathering)
SurvivorCore turns any part/mesh into a harvestable node via the Gatherable component, with
two interaction styles:
- Bare-hand — walk up, hold E (a
ProximityPrompt). For reeds, berries, loose stone. - Tool-swing — equip a tool, click to swing. The node requires a tool type (e.g. an axe) and takes several hits. This is the engine's first client-input → server-validated-hit pipeline (combat reuses it later).
Every hit is resolved server-side by src/systems/Harvesting.luau:
it validates, rolls a per-hit yield (yieldMin..yieldMax), tries to add it to the inventory
(blocking the hit if you're full — nothing wasted), then spends one HP and fires the hooks. On
depletion the node is destroyed (unless it opts out — see reactions).
Defining a node
A node's stats come from a resource def (preferred) or raw per-node attributes.
Resource defs (recommended)
Register once, reuse on every node — in code or no-code via the admin plugin:
SurvivorCore.Resources.register({
id = "oak_tree",
item = "wood", -- the item it yields (an Items id)
hp = 5, -- hits/gathers to deplete
requireTool = "axe", -- tool type required ("" = bare-hand)
yieldMin = 1,
yieldMax = 2, -- random yield per hit
})
Then a creator builds a mesh, tags it Gatherable (CollectionService), and sets one attribute:
| Attribute | Meaning |
|---|---|
Resource |
the resource def id to inherit from (e.g. "oak_tree") |
Raw attributes (overrides / quick nodes)
Any of these override the resource def (or stand alone without one):
| Attribute | Default | Meaning |
|---|---|---|
ItemId |
"" |
item yielded |
HP |
3 |
hits to deplete |
RequireTool |
"" |
tool type needed; "" = bare-hand |
YieldMin / YieldMax |
1 / 1 |
random yield per hit (or legacy single Yield) |
Interaction |
"auto" |
"auto" (tool if RequireTool set, else prompt), "prompt", or "tool" |
DestroyOnDeplete |
true |
false keeps the node so a reaction can transform it (stump/fell) |
Tools
Tool-swing needs a held Roblox Tool whose ToolType attribute matches the node's
RequireTool (falling back to the Tool's Name). The hotbar does this for you: give an item a
toolType field and a category of "tool", and selecting it in the hotbar equips a real Tool
(the hotbar→Tool bridge, src/systems/ToolEquip.luau). The
Tool's look is content-free: it clones a creator template from
ReplicatedStorage.SurvivorCoreContent.Tools (a Tool named by item id), or builds a minimal
default if none is registered.
Reactions (the juice)
Global Hooks.on("gather:hit"/"gather:depleted"/"gather:blocked", …) fire for every node. For
behavior tied to one resource type, use the reaction API — no core edits:
SurvivorCore.Gather.onReaction("reed_bush", "hit", function(ctx)
-- ctx = { instance, player, resource, item, granted, hpLeft, position }
sway(ctx.instance)
end)
SurvivorCore.Gather.onReaction("oak_tree", "depleted", function(ctx)
rezStumpAndFell(ctx.instance) -- the node had DestroyOnDeplete = false
end)
Tuning
Config.override("Harvesting", { SwingRange = 12, SwingCooldown = 0.45, RequireLineOfSight = true }).
Out of scope (for now)
Durability, multi-phase tree growth/regrow, and particle systems are creator content via the hooks above — the engine ships none. Combat (#12) reuses this same swing pipeline.