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