| |
|
| |
|
| | 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")
|
| |
|
| |
|
| | 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
|
| |
|
| |
|
| | 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
|
| |
|
| |
|
| | 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
|
| |
|
| |
|
| | 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
|
| |
|
| |
|
| | drop.Touched:Connect(function(hit)
|
| | local touchedPlayer = Players:GetPlayerFromCharacter(hit.Parent)
|
| | if touchedPlayer and touchedPlayer.UserId == player.UserId then
|
| |
|
| | 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)
|
| |
|
| |
|
| | 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)
|
| |
|
| |
|
| | createChopParticles(hitPos, ChoppingConfig.TreeTypes[treeType].LogColor)
|
| |
|
| |
|
| | if weaponType == "Fist" then
|
| | SoundEvent:FireAllClients("AxeSwing", hitPos)
|
| | else
|
| | SoundEvent:FireAllClients("AxeHitWood", hitPos)
|
| | end
|
| |
|
| |
|
| | dropWoodResource(hitPos, treeType, player)
|
| |
|
| | if health <= 0 then
|
| | local segmentSizeY = segment.Size.Y
|
| |
|
| |
|
| | if _G.IncrementStat then
|
| | _G.IncrementStat(player, "WoodChopped", 1)
|
| | end
|
| |
|
| |
|
| | if _G.ProgressQuest then
|
| | _G.ProgressQuest(player, "Chop", 1)
|
| | end
|
| |
|
| |
|
| | if _G.CheckAchievements then
|
| | task.defer(function() _G.CheckAchievements(player) end)
|
| | end
|
| |
|
| |
|
| | segment:SetAttribute("OwnerId", player.UserId)
|
| |
|
| | if segmentSizeY < ChoppingConfig.MinSegmentSizeY then
|
| |
|
| | 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)
|
| |
|
| |
|
| | if _G.AddHealthBar then
|
| | _G.AddHealthBar(bottomPiece)
|
| | _G.AddHealthBar(topPiece)
|
| | end
|
| |
|
| |
|
| | SoundEvent:FireAllClients("TreeFall", segment.Position)
|
| |
|
| | segment:Destroy()
|
| | end
|
| | end
|
| |
|
| | local function onChop(player, hitPart, hitPos)
|
| |
|
| | 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)
|
| |
|