TimberWoods / src /ServerScriptService /TreeSpawnerManager.server.lua
algorembrant's picture
Upload 88 files
0712d5f verified
-- src/ServerScriptService/TreeSpawnerManager.server.lua
-- Spawns trees on flat biome baseplates at Y=0
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local BiomeConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("BiomeConfig"))
local TreeModelConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("TreeModelConfig"))
local ChoppingConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ChoppingConfig"))
local treesFolder = Instance.new("Folder")
treesFolder.Name = "Trees"
treesFolder.Parent = workspace
local treePositions = {}
local function randomInRange(min, max)
return min + math.random() * (max - min)
end
local function isPositionValid(pos, minSpacing)
for _, existingPos in ipairs(treePositions) do
local dx = pos.X - existingPos.X
local dz = pos.Z - existingPos.Z
if (dx * dx + dz * dz) < (minSpacing * minSpacing) then
return false
end
end
return true
end
-- Add a health bar to a tree segment
local function addHealthBar(segment)
local treeType = segment:GetAttribute("TreeType")
local config = ChoppingConfig.TreeTypes[treeType]
if not config then return end
local billboard = Instance.new("BillboardGui")
billboard.Name = "HealthBarGui"
billboard.Size = UDim2.new(0, 60, 0, 8)
billboard.StudsOffset = Vector3.new(0, 1, 0)
billboard.AlwaysOnTop = false
billboard.MaxDistance = 30
billboard.Parent = segment
local bg = Instance.new("Frame")
bg.Name = "Background"
bg.Size = UDim2.new(1, 0, 1, 0)
bg.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
bg.BorderSizePixel = 0
bg.Parent = billboard
local bgCorner = Instance.new("UICorner")
bgCorner.CornerRadius = UDim.new(0, 3)
bgCorner.Parent = bg
local fill = Instance.new("Frame")
fill.Name = "Fill"
fill.Size = UDim2.new(1, 0, 1, 0)
fill.BackgroundColor3 = Color3.fromRGB(50, 200, 50)
fill.BorderSizePixel = 0
fill.Parent = bg
local fillCorner = Instance.new("UICorner")
fillCorner.CornerRadius = UDim.new(0, 3)
fillCorner.Parent = fill
local label = Instance.new("TextLabel")
label.Name = "HealthText"
label.Size = UDim2.new(1, 0, 1, 0)
label.BackgroundTransparency = 1
label.Text = tostring(config.HealthPerSegment)
label.TextColor3 = Color3.new(1, 1, 1)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.TextStrokeTransparency = 0.5
label.Parent = bg
segment:GetAttributeChangedSignal("Health"):Connect(function()
local maxHP = config.HealthPerSegment
local currentHP = segment:GetAttribute("Health") or maxHP
local ratio = math.clamp(currentHP / maxHP, 0, 1)
fill.Size = UDim2.new(ratio, 0, 1, 0)
label.Text = tostring(math.ceil(currentHP))
if ratio > 0.5 then
fill.BackgroundColor3 = Color3.fromRGB(math.floor(255 * (1 - ratio) * 2), 200, 50)
else
fill.BackgroundColor3 = Color3.fromRGB(255, math.floor(200 * ratio * 2), 50)
end
end)
end
-- Build a tree model at a position (Y=0 is the ground)
local function createTreeModel(treeType, position)
local template = TreeModelConfig.Templates[treeType]
if not template then return nil end
local config = ChoppingConfig.TreeTypes[treeType]
if not config then return nil end
local model = Instance.new("Model")
model.Name = treeType .. "_Tree"
local trunkDiameter = randomInRange(template.TrunkDiameter[1], template.TrunkDiameter[2])
local totalHeight = randomInRange(template.TrunkHeight[1], template.TrunkHeight[2])
local segmentCount = math.random(template.SegmentCount[1], template.SegmentCount[2])
local segmentHeight = totalHeight / segmentCount
-- Ground level is 0 -- trees sit directly on the baseplate
local groundY = 0
local currentY = groundY
for i = 1, segmentCount do
local segment = Instance.new("Part")
segment.Name = "TrunkSegment_" .. i
segment.Shape = Enum.PartType.Cylinder
local taperMult = 1 - ((i - 1) / segmentCount) * 0.4
local diameter = trunkDiameter * taperMult
segment.Size = Vector3.new(segmentHeight, diameter, diameter)
segment.CFrame = CFrame.new(position.X, currentY + segmentHeight / 2, position.Z) * CFrame.Angles(0, 0, math.rad(90))
segment.Color = template.BarkColor
segment.Material = template.BarkMaterial
segment.Anchored = true
segment.TopSurface = Enum.SurfaceType.Smooth
segment.BottomSurface = Enum.SurfaceType.Smooth
segment:SetAttribute("TreeType", treeType)
segment:SetAttribute("Health", config.HealthPerSegment)
segment.CustomPhysicalProperties = PhysicalProperties.new(config.Density, 0.3, 0.5)
CollectionService:AddTag(segment, "TreeSegment")
CollectionService:AddTag(segment, "Draggable")
if template.GlowTrunk then
local light = Instance.new("PointLight")
light.Color = template.BarkColor
light.Brightness = 0.5
light.Range = 8
light.Parent = segment
end
addHealthBar(segment)
segment.Parent = model
currentY = currentY + segmentHeight
if i > 1 then
local weld = Instance.new("WeldConstraint")
weld.Part0 = model:FindFirstChild("TrunkSegment_" .. (i - 1))
weld.Part1 = segment
weld.Parent = segment
end
end
-- Build canopy
if template.HasLeaves then
local canopyRadius = randomInRange(template.CanopyRadius[1], template.CanopyRadius[2])
local canopyY = currentY
if template.ConicalCanopy then
for layer = 1, template.CanopySegments do
local layerRadius = canopyRadius * (1 - (layer - 1) / template.CanopySegments)
local canopy = Instance.new("Part")
canopy.Name = "Canopy_" .. layer
canopy.Shape = Enum.PartType.Ball
canopy.Size = Vector3.new(layerRadius * 2, 2, layerRadius * 2)
canopy.Position = Vector3.new(position.X, canopyY + (layer - 1) * 1.5, position.Z)
canopy.Color = template.LeafColor
canopy.Material = template.LeafMaterial
canopy.Anchored = true
canopy.CanCollide = false
canopy.CastShadow = true
canopy.Parent = model
end
else
for seg = 1, template.CanopySegments do
local angle = (seg / template.CanopySegments) * math.pi * 2
local offsetX = math.cos(angle) * canopyRadius * 0.4
local offsetZ = math.sin(angle) * canopyRadius * 0.4
local size = randomInRange(canopyRadius * 0.6, canopyRadius * 1.0)
local canopy = Instance.new("Part")
canopy.Name = "Canopy_" .. seg
canopy.Shape = Enum.PartType.Ball
canopy.Size = Vector3.new(size * 2, size * 1.2, size * 2)
canopy.Position = Vector3.new(
position.X + offsetX,
canopyY + randomInRange(-1, 2),
position.Z + offsetZ
)
canopy.Color = template.LeafColor
canopy.Material = template.LeafMaterial
canopy.Anchored = true
canopy.CanCollide = false
canopy.CastShadow = true
canopy.Parent = model
end
end
end
local primaryPart = model:FindFirstChild("TrunkSegment_1")
if primaryPart then
model.PrimaryPart = primaryPart
end
CollectionService:AddTag(model, "TreeModel")
model.Parent = treesFolder
return model
end
-- Spawn trees in a biome region
local function spawnTreesInBiome(biomeName, biomeData)
local region = biomeData.Region
local treeTypes = biomeData.TreeTypes
if #treeTypes == 0 then return end
local regionWidth = region.MaxX - region.MinX
local regionDepth = region.MaxZ - region.MinZ
local area = regionWidth * regionDepth
local numTrees = math.floor(area * biomeData.TreeDensity)
numTrees = math.min(numTrees, 200)
local spawned = 0
local attempts = 0
local maxAttempts = numTrees * 5
while spawned < numTrees and attempts < maxAttempts do
attempts = attempts + 1
local x = region.MinX + math.random() * regionWidth
local z = region.MinZ + math.random() * regionDepth
if isPositionValid(Vector3.new(x, 0, z), BiomeConfig.MinTreeSpacing) then
local treeType = treeTypes[math.random(1, #treeTypes)]
-- All trees at Y=0 (flat baseplate surface)
local treePos = Vector3.new(x, 0, z)
local tree = createTreeModel(treeType, treePos)
if tree then
table.insert(treePositions, treePos)
spawned = spawned + 1
end
end
end
print("Spawned " .. spawned .. " trees in " .. biomeName)
end
-- Wait for biome generation then spawn trees
task.spawn(function()
while not _G.BiomeGenerationComplete do
task.wait(0.5)
end
print("=== Tree Spawning Starting ===")
for biomeName, biomeData in pairs(BiomeConfig.Biomes) do
spawnTreesInBiome(biomeName, biomeData)
task.wait()
end
print("=== Tree Spawning Complete ===")
_G.TreeSpawningComplete = true
end)
_G.CreateTree = function(treeType, position)
return createTreeModel(treeType, position)
end
_G.AddHealthBar = function(segment)
addHealthBar(segment)
end