TimberWoods / src /ServerScriptService /TreeManager.server.lua
algorembrant's picture
Upload 88 files
0712d5f verified
-- src/ServerScriptService/TreeManager.server.lua
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local ChoppingConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ChoppingConfig"))
local CraftingConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("CraftingConfig"))
local ChopEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("ChopEvent")
local NotificationEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("NotificationEvent")
local SoundEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("SoundEvent")
local function getEquippedAxeOrFist(player)
local axeType = player:GetAttribute("EquippedAxe")
-- No axe equipped or "None" = use fists
if not axeType or axeType == "" or axeType == "None" then
return CraftingConfig.Fist, "Fist"
end
local axeStats = ChoppingConfig.AxeTypes[axeType]
if axeStats then
return axeStats, axeType
end
-- Fallback to fists
return CraftingConfig.Fist, "Fist"
end
local function createChopParticles(position, color)
local attachment = Instance.new("Attachment")
attachment.WorldPosition = position
attachment.Parent = workspace.Terrain
local emitter = Instance.new("ParticleEmitter")
emitter.Color = ColorSequence.new(color or Color3.fromRGB(180, 140, 100))
emitter.Size = NumberSequence.new({
NumberSequenceKeypoint.new(0, 0.5),
NumberSequenceKeypoint.new(1, 0),
})
emitter.Lifetime = NumberRange.new(0.3, 0.8)
emitter.Rate = 0
emitter.Speed = NumberRange.new(5, 15)
emitter.SpreadAngle = Vector2.new(180, 180)
emitter.Parent = attachment
emitter:Emit(12)
task.delay(1, function()
attachment:Destroy()
end)
end
-- Drop a collectible wood resource near the tree
local function dropWoodResource(position, treeType, player)
if math.random() > CraftingConfig.WoodDropChance then return end
local amount = math.random(CraftingConfig.WoodDropAmount[1], CraftingConfig.WoodDropAmount[2])
local drop = Instance.new("Part")
drop.Name = "WoodDrop"
drop.Size = Vector3.new(1, 1, 1)
drop.Position = position + Vector3.new(math.random(-3, 3), 2, math.random(-3, 3))
drop.Shape = Enum.PartType.Block
drop.Material = Enum.Material.Wood
drop.Color = CraftingConfig.Resources.Wood.Color
drop.Anchored = false
drop.CanCollide = true
drop.CustomPhysicalProperties = PhysicalProperties.new(0.5, 0.3, 0.5)
drop:SetAttribute("ResourceType", "Wood")
drop:SetAttribute("Amount", amount)
drop:SetAttribute("OwnerId", player.UserId)
CollectionService:AddTag(drop, "ResourceDrop")
drop.Parent = workspace
-- Billboard showing amount
local bb = Instance.new("BillboardGui")
bb.Size = UDim2.new(0, 40, 0, 20)
bb.StudsOffset = Vector3.new(0, 1.5, 0)
bb.AlwaysOnTop = true
bb.Parent = drop
local label = Instance.new("TextLabel")
label.Size = UDim2.new(1, 0, 1, 0)
label.BackgroundTransparency = 1
label.Text = "+" .. tostring(amount) .. " Wood"
label.TextColor3 = Color3.fromRGB(200, 160, 80)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.TextStrokeTransparency = 0.3
label.Parent = bb
-- Auto-collect when player touches it
drop.Touched:Connect(function(hit)
local touchedPlayer = Players:GetPlayerFromCharacter(hit.Parent)
if touchedPlayer and touchedPlayer.UserId == player.UserId then
-- Add resources to player data
local data = _G.GetPlayerData(touchedPlayer)
if data then
if not data.Resources then
data.Resources = {}
end
data.Resources.Wood = (data.Resources.Wood or 0) + amount
NotificationEvent:FireClient(touchedPlayer, "Resource", "+" .. tostring(amount) .. " Wood (Total: " .. tostring(data.Resources.Wood) .. ")")
end
drop:Destroy()
end
end)
-- Auto-despawn after 30 seconds
task.delay(30, function()
if drop.Parent then drop:Destroy() end
end)
end
local function applyDamage(player, segment, hitPos, damage, weaponType)
if not CollectionService:HasTag(segment, "TreeSegment") then return end
local treeType = segment:GetAttribute("TreeType")
if not treeType or not ChoppingConfig.TreeTypes[treeType] then return end
local maxHealth = ChoppingConfig.TreeTypes[treeType].HealthPerSegment
local health = segment:GetAttribute("Health")
if not health then
health = maxHealth
end
health = health - damage
segment:SetAttribute("Health", health)
-- Visual feedback: chop particles
createChopParticles(hitPos, ChoppingConfig.TreeTypes[treeType].LogColor)
-- Sound feedback (different for fist vs axe)
if weaponType == "Fist" then
SoundEvent:FireAllClients("AxeSwing", hitPos) -- lighter sound
else
SoundEvent:FireAllClients("AxeHitWood", hitPos)
end
-- Drop wood resources on each hit
dropWoodResource(hitPos, treeType, player)
if health <= 0 then
local segmentSizeY = segment.Size.Y
-- Track stat
if _G.IncrementStat then
_G.IncrementStat(player, "WoodChopped", 1)
end
-- Progress quests
if _G.ProgressQuest then
_G.ProgressQuest(player, "Chop", 1)
end
-- Check achievements
if _G.CheckAchievements then
task.defer(function() _G.CheckAchievements(player) end)
end
-- Set ownership on the log pieces
segment:SetAttribute("OwnerId", player.UserId)
if segmentSizeY < ChoppingConfig.MinSegmentSizeY then
-- Drop bonus resources when segment destroyed
dropWoodResource(segment.Position, treeType, player)
segment:Destroy()
return
end
local localHitPos = segment.CFrame:ToObjectSpace(CFrame.new(hitPos)).Position
local yPos = localHitPos.Y
local totalHeight = segment.Size.Y
local bottomHeight = (totalHeight / 2) + yPos
local topHeight = totalHeight - bottomHeight
if bottomHeight < 0.2 or topHeight < 0.2 then
return
end
local bottomPiece = segment:Clone()
local topPiece = segment:Clone()
bottomPiece.Parent = segment.Parent
topPiece.Parent = segment.Parent
bottomPiece.Size = Vector3.new(segment.Size.X, bottomHeight, segment.Size.Z)
topPiece.Size = Vector3.new(segment.Size.X, topHeight, segment.Size.Z)
local offsetBottom = CFrame.new(0, -topHeight / 2, 0)
local offsetTop = CFrame.new(0, bottomHeight / 2, 0)
bottomPiece.CFrame = segment.CFrame * offsetBottom
topPiece.CFrame = segment.CFrame * offsetTop
bottomPiece:SetAttribute("Health", maxHealth)
topPiece:SetAttribute("Health", maxHealth)
bottomPiece:SetAttribute("OwnerId", player.UserId)
topPiece:SetAttribute("OwnerId", player.UserId)
bottomPiece.Anchored = false
topPiece.Anchored = false
bottomPiece.CustomPhysicalProperties = PhysicalProperties.new(ChoppingConfig.TreeTypes[treeType].Density, 0.3, 0.5)
topPiece.CustomPhysicalProperties = PhysicalProperties.new(ChoppingConfig.TreeTypes[treeType].Density, 0.3, 0.5)
-- Add health bars to split pieces
if _G.AddHealthBar then
_G.AddHealthBar(bottomPiece)
_G.AddHealthBar(topPiece)
end
-- Sound: tree fall
SoundEvent:FireAllClients("TreeFall", segment.Position)
segment:Destroy()
end
end
local function onChop(player, hitPart, hitPos)
-- Anti-cheat: rate limit
if _G.IsRateLimited and _G.IsRateLimited(player, "Chop") then return end
local char = player.Character
if not char or not char:FindFirstChild("Head") then return end
local axeStats, weaponType = getEquippedAxeOrFist(player)
local distance = (char.Head.Position - hitPos).Magnitude
if distance > axeStats.Range + 2 then
return
end
applyDamage(player, hitPart, hitPos, axeStats.Damage, weaponType)
end
ChopEvent.OnServerEvent:Connect(onChop)