File size: 7,792 Bytes
0712d5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
-- 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)