Files

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).

📹 Demo: chopping a tree, gathering reeds & hand crafting

Defining a node

A node's stats come from a resource def (preferred) or raw per-node attributes.

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.