mirror of
https://github.com/TemujinCalidius/SurvivorCore.git
synced 2026-08-14 00:58:01 +00:00
Promote CHANGELOG Unreleased → 0.10.0 (the schema-driven Build page for world objects, #11) and bump wally.toml + SurvivorCore.VERSION. Demo: new demo/server/Stage.luau — one switch for what the demo places in the world (gather field + quest post, mobs, starting inventory, hand-test props). The test-station prop rows now default OFF: they're scaffolding for working ON the engine and crowded the spawn area for everyone else. Collateral: the Build demo video on the docs Demos line and as an 8th site video tile; the landing page's Studio card now tells the Build story; version surfaces bumped across site, README and getting-started. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
651 lines
22 KiB
Luau
651 lines
22 KiB
Luau
--[[
|
|
Demo boot script. Shows both extension layers:
|
|
1. registering content from code, and
|
|
2. reacting to creator-owned components via hooks.
|
|
|
|
Try it: serve `demo.project.json` into a place, add a Part to Workspace, tag it
|
|
"Gatherable" (CollectionService), set attributes ItemId="reed", Yield=2, HP=3,
|
|
then play and interact with it — the reed lands in your inventory (press Tab).
|
|
]]
|
|
|
|
local Players = game:GetService("Players")
|
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
local SurvivorCore = require(ReplicatedStorage:WaitForChild("SurvivorCore"))
|
|
|
|
-- What this demo places in the world — see demo/server/Stage.luau to turn any of it off.
|
|
local STAGE = require(script.Parent.Stage)
|
|
|
|
-- 1. Programmatic content -----------------------------------------------------
|
|
-- NOTE: the `icon` ids below are PLACEHOLDERS (the engine's stat icons) so the demo grid
|
|
-- isn't empty. A real game registers proper item icons (per-item `icon` or the "ItemIcons"
|
|
-- Assets category). The engine itself ships zero item content.
|
|
SurvivorCore.Items.register({
|
|
id = "reed",
|
|
name = "Reed",
|
|
description = "A tough riverside stalk. Weave it into baskets and cordage.",
|
|
stack = 20,
|
|
weight = 0.05,
|
|
category = "material",
|
|
icon = "rbxassetid://102789813187589",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "reed_basket",
|
|
name = "Reed Basket",
|
|
description = "A simple woven basket.",
|
|
stack = 1,
|
|
weight = 0.5,
|
|
category = "container",
|
|
icon = "rbxassetid://137051934393677",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "berry",
|
|
name = "Wild Berries",
|
|
description = "A small handful of tart berries. Eases hunger and thirst a little.",
|
|
stack = 20,
|
|
weight = 0.05,
|
|
category = "consumable",
|
|
onConsume = { Hunger = -15, Thirst = -5 },
|
|
icon = "rbxassetid://138699077112926",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "mushroom",
|
|
name = "Cave Mushroom",
|
|
description = "Filling — but eating it raw might make you sick.",
|
|
stack = 10,
|
|
weight = 0.1,
|
|
category = "consumable",
|
|
onConsume = { Hunger = -10, Health = 5, Poison = 15 },
|
|
icon = "rbxassetid://86406240258610",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "water_skin",
|
|
name = "Water Skin",
|
|
description = "A full skin of water. Drink to quench your thirst.",
|
|
stack = 1,
|
|
weight = 0.5,
|
|
category = "tool",
|
|
onConsume = { Thirst = -40 },
|
|
icon = "rbxassetid://124374881225649",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "stone_axe",
|
|
name = "Stone Axe",
|
|
description = "A crude chopping tool. Equip it from the hotbar, then click a tree to chop.",
|
|
stack = 1,
|
|
weight = 1.5,
|
|
category = "tool",
|
|
toolType = "axe", -- the ToolEquip bridge gives the held Tool this ToolType; trees require "axe"
|
|
icon = "rbxassetid://129856164091801",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "wood",
|
|
name = "Wood",
|
|
description = "A length of timber chopped from a tree. Craft it into planks.",
|
|
stack = 20,
|
|
weight = 0.4,
|
|
category = "material",
|
|
icon = "rbxassetid://102789813187589",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "plank",
|
|
name = "Wooden Plank",
|
|
description = "Milled from wood at hand. A basic building material.",
|
|
stack = 20,
|
|
weight = 0.3,
|
|
category = "material",
|
|
icon = "rbxassetid://137051934393677",
|
|
})
|
|
|
|
-- Weapons are just items: a `toolType` (so the hotbar equips them) + flat `weapon*` stats the Combat
|
|
-- system reads. Equip from the hotbar, then click (melee) or hold-to-draw + release (bow).
|
|
SurvivorCore.Items.register({
|
|
id = "wood_club",
|
|
name = "Wooden Club",
|
|
description = "A heavy length of timber. Equip it, then click a creature to swing.",
|
|
stack = 1,
|
|
weight = 1.8,
|
|
category = "weapon",
|
|
toolType = "club",
|
|
weaponKind = "melee",
|
|
weaponDamage = 20,
|
|
weaponRange = 8,
|
|
weaponCooldown = 0.6,
|
|
icon = "rbxassetid://102789813187589",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "short_bow",
|
|
name = "Short Bow",
|
|
description = "Hold right-click to aim, hold left-click to draw (longer = stronger), release to fire.",
|
|
stack = 1,
|
|
weight = 1.2,
|
|
category = "weapon",
|
|
toolType = "bow",
|
|
weaponKind = "bow",
|
|
weaponDamage = 30,
|
|
weaponDrawTime = 1,
|
|
weaponProjectileSpeed = 210,
|
|
weaponMaxRange = 260,
|
|
weaponAmmo = "arrow", -- a balanced, flat-ish arrow (consumed from the inventory per shot)
|
|
icon = "rbxassetid://124374881225649",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "war_bow",
|
|
name = "War Bow",
|
|
description = "A heavier bow — slower pull, harder hit. Loads heavy arrows that arc steeply.",
|
|
stack = 1,
|
|
weight = 1.8,
|
|
category = "weapon",
|
|
toolType = "bow",
|
|
weaponKind = "bow",
|
|
weaponDamage = 42,
|
|
weaponDrawTime = 1.4, -- slower to full draw
|
|
weaponProjectileSpeed = 180,
|
|
weaponMaxRange = 300,
|
|
weaponAmmo = "heavy_arrow",
|
|
icon = "rbxassetid://124374881225649",
|
|
})
|
|
|
|
-- Two arrow TYPES with different ballistics — exactly what the admin "Arrows / Ammo" editor authors.
|
|
-- Hunting: a knife to butcher carcasses, and the raw meat they yield (risky eaten raw — cook it
|
|
-- once stations arrive).
|
|
SurvivorCore.Items.register({
|
|
id = "stone_knife",
|
|
name = "Stone Knife",
|
|
description = "A keen flake of stone. Equip it to butcher carcasses.",
|
|
stack = 1,
|
|
weight = 0.6,
|
|
category = "tool",
|
|
toolType = "knife",
|
|
icon = "rbxassetid://129856164091801",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "raw_meat",
|
|
name = "Raw Meat",
|
|
description = "Fresh from the hunt. Eating it raw fills you up — and turns your stomach.",
|
|
stack = 10,
|
|
weight = 0.3,
|
|
category = "consumable",
|
|
onConsume = { Hunger = -25, Poison = 8 },
|
|
icon = "rbxassetid://138699077112926",
|
|
})
|
|
|
|
SurvivorCore.Items.register({
|
|
id = "arrow",
|
|
name = "Arrow",
|
|
description = "A balanced arrow. Flies fairly flat.",
|
|
category = "ammo",
|
|
weight = 0.05,
|
|
stack = 32,
|
|
ammoDamage = 1, -- the bow's base damage
|
|
ammoDrop = 1, -- normal gravity / curve
|
|
ammoSpeed = 0, -- 0 = use the bow's speed
|
|
ammoRange = 0, -- 0 = use the bow's max range
|
|
icon = "rbxassetid://86406240258610",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "heavy_arrow",
|
|
name = "Heavy Arrow",
|
|
description = "Heavier head: hits harder and drops fast (a steep arc), but won't reach far.",
|
|
category = "ammo",
|
|
weight = 0.12,
|
|
stack = 16,
|
|
ammoDamage = 1.4, -- +40% damage
|
|
ammoDrop = 2, -- doubles gravity → a pronounced arc
|
|
ammoSpeed = 0.85, -- a touch slower
|
|
ammoRange = 140, -- short effective range
|
|
icon = "rbxassetid://86406240258610",
|
|
})
|
|
|
|
-- Gatherable RESOURCE defs: what a tagged node *is*. A creator tags a mesh "Gatherable" and sets
|
|
-- Resource = "<id>" to inherit these (no per-node attributes). HP>0 + a tool = click-to-swing;
|
|
-- no tool = bare-hand hold-E. (These can also be authored no-code via the admin plugin.)
|
|
SurvivorCore.Resources.register({
|
|
id = "oak_tree",
|
|
item = "wood",
|
|
hp = 5,
|
|
requireTool = "axe", -- needs an axe equipped
|
|
yieldMin = 1,
|
|
yieldMax = 2,
|
|
})
|
|
SurvivorCore.Resources.register({
|
|
id = "reed_bush",
|
|
item = "reed",
|
|
hp = 3,
|
|
requireTool = "", -- bare-hand
|
|
yieldMin = 1,
|
|
yieldMax = 2,
|
|
})
|
|
|
|
-- Mob defs: what a "Mob"-tagged model *is*. `faction` picks the AI profile (hostile chases + attacks;
|
|
-- passive flees). The engine ships zero creatures — these are demo content (also no-code-authorable).
|
|
SurvivorCore.Mobs.register({
|
|
id = "husk",
|
|
faction = "hostile",
|
|
health = 60,
|
|
walkSpeed = 5,
|
|
runSpeed = 15,
|
|
aggroRange = 40,
|
|
leashRange = 70,
|
|
attackRange = 6,
|
|
attackDamage = 8,
|
|
attackCooldown = 1.5,
|
|
})
|
|
SurvivorCore.Mobs.register({
|
|
id = "boar",
|
|
faction = "passive",
|
|
health = 40,
|
|
walkSpeed = 6,
|
|
runSpeed = 24,
|
|
aggroRange = 28, -- bolts when you get within ~28 studs
|
|
-- Hunting: a slain boar leaves a butcherable carcass (a Gatherable — knife required).
|
|
carcassItem = "raw_meat",
|
|
carcassHp = 3,
|
|
carcassTool = "knife",
|
|
carcassYieldMin = 1,
|
|
carcassYieldMax = 2,
|
|
})
|
|
-- (The husk deliberately has NO carcass fields — blank carcassItem = no carcass on death.)
|
|
|
|
-- Quests: a small chain through the demo loop — gather → craft → fight. `gather_reeds` starts
|
|
-- automatically; finishing it auto-starts `weave_basket` (requires + autoStart); `slay_husk` is
|
|
-- offered at the quest-giver post near spawn and must be turned in there (turnIn).
|
|
SurvivorCore.Quests.register({
|
|
id = "gather_reeds",
|
|
name = "Gather Reeds",
|
|
description = "Pull 3 reeds from the bushes by the river.",
|
|
objectives = { { type = "gather", target = "reed", count = 3 } },
|
|
rewards = { { item = "berry", count = 2 } },
|
|
autoStart = true,
|
|
})
|
|
SurvivorCore.Quests.register({
|
|
id = "weave_basket",
|
|
name = "Weave a Basket",
|
|
description = "Craft a reed basket from your gathered reeds.",
|
|
objectives = { { type = "craft", target = "reed_basket", count = 1 } },
|
|
rewards = { { item = "arrow", count = 5 } },
|
|
autoStart = true,
|
|
requires = "gather_reeds",
|
|
})
|
|
SurvivorCore.Quests.register({
|
|
id = "slay_husk",
|
|
name = "Slay a Husk",
|
|
description = "A husk stalks the field. Put it down, then report back.",
|
|
objectives = { { type = "kill", target = "husk", count = 1 } },
|
|
rewards = { { item = "heavy_arrow", count = 4 } },
|
|
turnIn = true, -- return to the giver post to claim
|
|
})
|
|
SurvivorCore.Quests.register({
|
|
id = "hunt_boar",
|
|
name = "The Hunt",
|
|
description = "Track the boar, take it down, and butcher it for meat.",
|
|
objectives = { -- multi-objective: both must complete
|
|
{ type = "kill", target = "boar", count = 1 },
|
|
{ type = "gather", target = "raw_meat", count = 2 },
|
|
},
|
|
rewards = { { item = "berry", count = 4 } },
|
|
autoStart = true,
|
|
})
|
|
|
|
-- Achievements: flat counter + threshold against the engine's auto-derived counters.
|
|
SurvivorCore.Achievements.register({
|
|
key = "first_reed",
|
|
name = "First Harvest",
|
|
description = "Gather your first reed.",
|
|
counter = "gathers_reed",
|
|
threshold = 1,
|
|
})
|
|
SurvivorCore.Achievements.register({
|
|
key = "reed_hoarder",
|
|
name = "Reed Hoarder",
|
|
description = "Gather 25 reeds.",
|
|
counter = "gathers_reed",
|
|
threshold = 25,
|
|
})
|
|
SurvivorCore.Achievements.register({
|
|
key = "handy",
|
|
name = "Handy",
|
|
description = "Craft your first item.",
|
|
counter = "crafts_total",
|
|
threshold = 1,
|
|
})
|
|
SurvivorCore.Achievements.register({
|
|
key = "husk_slayer",
|
|
name = "Husk Slayer",
|
|
description = "Slay 3 husks.",
|
|
counter = "kills_husk",
|
|
threshold = 3,
|
|
})
|
|
SurvivorCore.Achievements.register({
|
|
key = "hunter",
|
|
name = "Hunter",
|
|
description = "Bring down your first boar.",
|
|
counter = "kills_boar",
|
|
threshold = 1,
|
|
})
|
|
SurvivorCore.Achievements.register({
|
|
key = "butcher",
|
|
name = "Butcher",
|
|
description = "Carve 3 cuts of raw meat.",
|
|
counter = "gathers_raw_meat",
|
|
threshold = 3,
|
|
})
|
|
SurvivorCore.Achievements.register({
|
|
key = "hard_way",
|
|
name = "The Hard Way",
|
|
description = "Die. It happens to everyone.",
|
|
counter = "deaths_total",
|
|
threshold = 1,
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "straw_hat",
|
|
name = "Straw Hat",
|
|
description = "Keeps the sun off. Equips to the head slot.",
|
|
stack = 1,
|
|
weight = 0.2,
|
|
category = "apparel",
|
|
equipment = { slot = "head" },
|
|
icon = "rbxassetid://88514622686548",
|
|
})
|
|
SurvivorCore.Items.register({
|
|
id = "reed_satchel",
|
|
name = "Reed Satchel",
|
|
description = "A woven back-satchel. Equip it for +6 slots and +10 kg of carry weight.",
|
|
stack = 1,
|
|
weight = 0.8,
|
|
category = "container",
|
|
equipment = { slot = "back" },
|
|
backpack = { slots = 6, maxWeight = 10 },
|
|
icon = "rbxassetid://137051934393677",
|
|
})
|
|
|
|
SurvivorCore.Recipes.register({
|
|
id = "reed_basket",
|
|
station = "hand",
|
|
ingredients = { { item = "reed", count = 5 } },
|
|
output = { item = "reed_basket", count = 1 },
|
|
})
|
|
SurvivorCore.Recipes.register({
|
|
id = "plank",
|
|
station = "hand",
|
|
ingredients = { { item = "wood", count = 2 } },
|
|
output = { item = "plank", count = 1 },
|
|
})
|
|
|
|
-- 2. React to creator-owned Gatherables (the engine grants the per-hit yield into the inventory).
|
|
SurvivorCore.Hooks.on("gather:depleted", function(ctx)
|
|
print(("[demo] %s fully gathered %s"):format(ctx.player.Name, tostring(ctx.resource or ctx.item)))
|
|
end)
|
|
|
|
-- A flourish hook: print whenever a consumable is used.
|
|
SurvivorCore.Hooks.on("item:use", function(ctx)
|
|
print(("[demo] %s used %s"):format(ctx.player.Name, tostring(ctx.itemId)))
|
|
end)
|
|
|
|
-- 3. Seed sample player state so the HUD + inventory render populated in the demo.
|
|
-- A real game sets these itself (or restores them from a DataStore).
|
|
local function seedPlayer(player: Player)
|
|
player:SetAttribute("Credits", 250)
|
|
|
|
-- A starter inventory. The satchel is equipped (EquipSlot_Back) for the extra slots its backpack
|
|
-- bonus grants, leaving room for the gather tool, weapons and arrows.
|
|
player:SetAttribute("InvSlot_1", "berry")
|
|
player:SetAttribute("InvQty_1", 12)
|
|
player:SetAttribute("InvSlot_2", "mushroom")
|
|
player:SetAttribute("InvQty_2", 3)
|
|
player:SetAttribute("InvSlot_3", "water_skin")
|
|
player:SetAttribute("InvQty_3", 1)
|
|
player:SetAttribute("InvSlot_4", "arrow")
|
|
player:SetAttribute("InvQty_4", 24)
|
|
player:SetAttribute("InvSlot_5", "stone_axe")
|
|
player:SetAttribute("InvQty_5", 1)
|
|
-- Weapons live in real inventory slots (so a hotbar pin always has a backing stack); the satchel's
|
|
-- +6 slots make room.
|
|
player:SetAttribute("InvSlot_6", "wood_club")
|
|
player:SetAttribute("InvQty_6", 1)
|
|
player:SetAttribute("InvSlot_7", "short_bow")
|
|
player:SetAttribute("InvQty_7", 1)
|
|
player:SetAttribute("InvSlot_8", "war_bow")
|
|
player:SetAttribute("InvQty_8", 1)
|
|
player:SetAttribute("InvSlot_9", "heavy_arrow")
|
|
player:SetAttribute("InvQty_9", 12)
|
|
player:SetAttribute("InvSlot_10", "stone_knife")
|
|
player:SetAttribute("InvQty_10", 1)
|
|
player:SetAttribute("HotbarSlot1", "berry") -- a pre-pinned quick slot
|
|
player:SetAttribute("HotbarSlot2", "stone_axe") -- press 2 to equip the axe, then click a tree
|
|
player:SetAttribute("HotbarSlot3", "wood_club") -- press 3 to equip the club, then click a mob
|
|
player:SetAttribute("HotbarSlot4", "short_bow") -- press 4: aim (RMB) + draw (LMB) — balanced arrows
|
|
player:SetAttribute("HotbarSlot5", "war_bow") -- press 5: the war bow — heavy arrows that arc steeply
|
|
player:SetAttribute("HotbarSlot6", "stone_knife") -- press 6: the knife — butcher a boar carcass
|
|
player:SetAttribute("EquipSlot_Head", "straw_hat") -- a pre-filled equipment slot
|
|
player:SetAttribute("EquipSlot_Back", "reed_satchel") -- equipped for +slots / +carry weight
|
|
end
|
|
if STAGE.SeedInventory then
|
|
for _, player in Players:GetPlayers() do
|
|
seedPlayer(player)
|
|
end
|
|
Players.PlayerAdded:Connect(seedPlayer)
|
|
end
|
|
|
|
SurvivorCore.start()
|
|
|
|
-- 4. The demo "gather field": a tool-gated tree + a bare-hand reed, the axe Tool template the
|
|
-- hotbar equips, and the reaction "juice" (reed sways on hit; the tree fells + leaves a stump).
|
|
-- All demo content — the engine ships none of it. Built after start() so the resource defs are
|
|
-- registered before the nodes bind.
|
|
local CollectionService = game:GetService("CollectionService")
|
|
local TweenService = game:GetService("TweenService")
|
|
local Workspace = game:GetService("Workspace")
|
|
|
|
-- The axe Tool the ToolEquip bridge clones when "stone_axe" is the active hotbar item.
|
|
local function buildAxeTemplate()
|
|
local content = ReplicatedStorage:FindFirstChild("SurvivorCoreContent")
|
|
if not content then
|
|
content = Instance.new("Folder")
|
|
content.Name = "SurvivorCoreContent"
|
|
content.Parent = ReplicatedStorage
|
|
end
|
|
local tools = content:FindFirstChild("Tools")
|
|
if not tools then
|
|
tools = Instance.new("Folder")
|
|
tools.Name = "Tools"
|
|
tools.Parent = content
|
|
end
|
|
if tools:FindFirstChild("stone_axe") then
|
|
return
|
|
end
|
|
local tool = Instance.new("Tool")
|
|
tool.Name = "stone_axe"
|
|
tool.RequiresHandle = true
|
|
tool.CanBeDropped = false
|
|
local handle = Instance.new("Part")
|
|
handle.Name = "Handle"
|
|
handle.Size = Vector3.new(0.6, 3, 0.6)
|
|
handle.Color = Color3.fromRGB(110, 80, 55)
|
|
handle.Material = Enum.Material.Wood
|
|
handle.CanCollide = false
|
|
handle.Massless = true
|
|
handle.Parent = tool
|
|
tool.Parent = tools
|
|
end
|
|
buildAxeTemplate()
|
|
|
|
local function buildTree(position: Vector3)
|
|
local trunk = Instance.new("Part")
|
|
trunk.Name = "OakTree"
|
|
trunk.Anchored = true
|
|
trunk.Size = Vector3.new(2, 14, 2)
|
|
trunk.Position = position + Vector3.new(0, 7, 0)
|
|
trunk.Color = Color3.fromRGB(95, 70, 45)
|
|
trunk.Material = Enum.Material.Wood
|
|
|
|
local leaves = Instance.new("Part")
|
|
leaves.Name = "Leaves"
|
|
leaves.Shape = Enum.PartType.Ball
|
|
leaves.Size = Vector3.new(9, 9, 9)
|
|
leaves.Position = position + Vector3.new(0, 15, 0)
|
|
leaves.Color = Color3.fromRGB(70, 130, 70)
|
|
leaves.Material = Enum.Material.Grass
|
|
leaves.Anchored = false
|
|
leaves.CanCollide = false
|
|
leaves.Massless = true
|
|
leaves.Parent = trunk
|
|
local weld = Instance.new("WeldConstraint")
|
|
weld.Part0 = trunk
|
|
weld.Part1 = leaves
|
|
weld.Parent = trunk
|
|
|
|
trunk:SetAttribute("Resource", "oak_tree")
|
|
trunk:SetAttribute("DestroyOnDeplete", false) -- the fell reaction transforms it instead
|
|
CollectionService:AddTag(trunk, "Gatherable")
|
|
trunk.Parent = Workspace
|
|
end
|
|
|
|
local function buildReed(position: Vector3)
|
|
local reed = Instance.new("Part")
|
|
reed.Name = "ReedBush"
|
|
reed.Anchored = true
|
|
reed.Size = Vector3.new(2, 4, 2)
|
|
reed.Position = position + Vector3.new(0, 2, 0)
|
|
reed.Color = Color3.fromRGB(110, 160, 90)
|
|
reed.Material = Enum.Material.Grass
|
|
reed:SetAttribute("_Rest", reed.CFrame)
|
|
reed:SetAttribute("Resource", "reed_bush")
|
|
CollectionService:AddTag(reed, "Gatherable")
|
|
reed.Parent = Workspace
|
|
end
|
|
|
|
if STAGE.WorldContent then
|
|
buildTree(Vector3.new(10, 0, 28))
|
|
buildTree(Vector3.new(18, 0, 28))
|
|
buildReed(Vector3.new(-8, 0, 28))
|
|
buildReed(Vector3.new(-12, 0, 28))
|
|
end
|
|
|
|
-- Reaction juice (per resource type) — pure creator content via the engine's reaction API.
|
|
local SWAY = TweenInfo.new(0.1, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, true)
|
|
SurvivorCore.Gather.onReaction("reed_bush", "hit", function(ctx)
|
|
local reed = ctx.instance
|
|
if reed and reed:IsA("BasePart") then
|
|
local rest = reed:GetAttribute("_Rest") or reed.CFrame
|
|
TweenService:Create(reed, SWAY, { CFrame = rest * CFrame.Angles(0, 0, math.rad(15)) }):Play()
|
|
end
|
|
end)
|
|
SurvivorCore.Gather.onReaction("oak_tree", "depleted", function(ctx)
|
|
local trunk = ctx.instance
|
|
if not trunk or not trunk:IsA("BasePart") then
|
|
return
|
|
end
|
|
local stump = Instance.new("Part")
|
|
stump.Name = "Stump"
|
|
stump.Anchored = true
|
|
stump.Size = Vector3.new(2.4, 1.2, 2.4)
|
|
stump.Position = Vector3.new(trunk.Position.X, trunk.Position.Y - trunk.Size.Y / 2 + 0.6, trunk.Position.Z)
|
|
stump.Color = Color3.fromRGB(95, 70, 45)
|
|
stump.Material = Enum.Material.Wood
|
|
stump.Parent = Workspace
|
|
-- Let physics topple the trunk (leaves welded along for the ride), then clean up.
|
|
trunk.Anchored = false
|
|
trunk.AssemblyAngularVelocity = Vector3.new(0, 0, 6)
|
|
task.delay(4, function()
|
|
if trunk and trunk.Parent then
|
|
trunk:Destroy()
|
|
end
|
|
end)
|
|
end)
|
|
|
|
-- 5. The demo "combat field": weapon Tool looks the hotbar equips, a hostile husk that chases +
|
|
-- attacks, and a passive boar that bolts when you near it. All demo content — the engine ships no
|
|
-- creatures or weapons. (Mobs.spawn is available after start(); the AI runs server-side.)
|
|
local function buildWeaponTemplates()
|
|
local content = ReplicatedStorage:FindFirstChild("SurvivorCoreContent")
|
|
local tools = content and content:FindFirstChild("Tools")
|
|
if not tools then
|
|
return
|
|
end
|
|
local function template(id: string, size: Vector3, color: Color3)
|
|
if tools:FindFirstChild(id) then
|
|
return
|
|
end
|
|
local tool = Instance.new("Tool")
|
|
tool.Name = id
|
|
tool.RequiresHandle = true
|
|
tool.CanBeDropped = false
|
|
local handle = Instance.new("Part")
|
|
handle.Name = "Handle"
|
|
handle.Size = size
|
|
handle.Color = color
|
|
handle.Material = Enum.Material.Wood
|
|
handle.CanCollide = false
|
|
handle.Massless = true
|
|
handle.Parent = tool
|
|
tool.Parent = tools
|
|
end
|
|
template("wood_club", Vector3.new(0.5, 3.5, 0.5), Color3.fromRGB(120, 85, 55))
|
|
template("short_bow", Vector3.new(0.3, 4, 0.3), Color3.fromRGB(150, 110, 70))
|
|
template("war_bow", Vector3.new(0.35, 5, 0.35), Color3.fromRGB(110, 80, 60))
|
|
template("stone_knife", Vector3.new(0.3, 1.6, 0.3), Color3.fromRGB(160, 160, 165))
|
|
end
|
|
buildWeaponTemplates()
|
|
|
|
-- Husks live out past their aggro+wander reach of spawn — new players shouldn't get farmed at
|
|
-- the spawn point before they've picked up a weapon (they'll still hunt you in their territory).
|
|
if STAGE.Mobs then
|
|
SurvivorCore.Mobs.spawn("husk", CFrame.new(70, 5, 45), { respawn = true })
|
|
SurvivorCore.Mobs.spawn("husk", CFrame.new(82, 5, 58), { respawn = true })
|
|
SurvivorCore.Mobs.spawn("boar", CFrame.new(-30, 5, 18))
|
|
end
|
|
|
|
-- The quest-giver post: offers `slay_husk` (hold E to accept; return to turn in). Any mesh works —
|
|
-- tag it "QuestGiver" + set Quest — this demo just uses a marked wooden post.
|
|
local function buildQuestPost(position: Vector3)
|
|
local post = Instance.new("Part")
|
|
post.Name = "QuestPost"
|
|
post.Size = Vector3.new(1, 5, 1)
|
|
post.Anchored = true
|
|
post.Color = Color3.fromRGB(150, 110, 70)
|
|
post.Material = Enum.Material.Wood
|
|
post.Position = position + Vector3.new(0, 2.5, 0)
|
|
local sign = Instance.new("Part")
|
|
sign.Name = "Sign"
|
|
sign.Size = Vector3.new(2.4, 1.4, 0.3)
|
|
sign.Color = Color3.fromRGB(120, 170, 255)
|
|
sign.Material = Enum.Material.SmoothPlastic
|
|
sign.Anchored = true
|
|
sign.CFrame = post.CFrame * CFrame.new(0, 1.6, 0)
|
|
sign.Parent = post
|
|
post:SetAttribute("Quest", "slay_husk")
|
|
CollectionService:AddTag(post, "QuestGiver")
|
|
post.Parent = Workspace
|
|
end
|
|
if STAGE.WorldContent then
|
|
buildQuestPost(Vector3.new(8, 0, 8))
|
|
end
|
|
|
|
-- Flourish hooks: print quest + achievement milestones to the output.
|
|
SurvivorCore.Hooks.on("quest:completed", function(ctx)
|
|
print(("[demo] %s completed quest '%s'"):format(ctx.player.Name, tostring(ctx.questId)))
|
|
end)
|
|
SurvivorCore.Hooks.on("achievement:unlocked", function(ctx)
|
|
print(("[demo] %s unlocked achievement '%s'"):format(ctx.player.Name, tostring(ctx.key)))
|
|
end)
|
|
|
|
-- Death juice (per mob type): fade the husk's body out over its corpse time. Pure creator content
|
|
-- via the reaction API — the engine just fires "died"; the game decides what a corpse looks like.
|
|
local FADE = TweenInfo.new(4, Enum.EasingStyle.Linear)
|
|
SurvivorCore.Mobs.onReaction("husk", "died", function(ctx)
|
|
local model = ctx.instance
|
|
if not model then
|
|
return
|
|
end
|
|
for _, part in model:GetDescendants() do
|
|
if part:IsA("BasePart") then
|
|
TweenService:Create(part, FADE, { Transparency = 1 }):Play()
|
|
end
|
|
end
|
|
end)
|
|
SurvivorCore.Hooks.on("mob:died", function(ctx)
|
|
print(("[demo] %s was slain"):format(tostring(ctx.mobType)))
|
|
end)
|
|
|
|
print("SurvivorCore demo booted — v" .. SurvivorCore.VERSION)
|