Need some quick help

Yeah, I literally just ChatGPT on how I should do it. I already have modules in my game but I think it would be more modular if I made the setup tower system a OOP system because right now it looks like this

(Server sends a placed tower)

-- COMPLETE FIXED TOWERS MODULE --
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")

local Animations = require(ReplicatedStorage.Modules.Animations)

local Towers = {}
local targetCache = {}
local mobPositions = {}

-- Pre-calculate mob positions
RunService.Heartbeat:Connect(function()
	mobPositions = {}
	for _, mob in ipairs(workspace.Hitboxes:GetChildren()) do
		if mob:FindFirstChild("Health") then
			mobPositions[mob] = mob.Position
		end
	end
end)

-- Improved targeting with validation
local function GetNextTarget(tower)
	-- Cache invalidation
	--local cached = targetCache[tower]
	--if cached then
	--	if not cached.Parent or not cached:FindFirstChild("Health") or cached.Health.Value <= 0 then
	--		targetCache[tower] = nil
	--		cached = nil
	--	else
	--		return cached
	--	end
	--end

	local Config = tower.Config
	local HRP = tower.HumanoidRootPart
	if not (Config and HRP) then return nil end

	local range = tower:GetAttribute("Range")
	local mode = Config:FindFirstChild("TargetingMode") and Config.TargetingMode.Value or "First"

	local candidates = {}
	for mob, position in pairs(mobPositions) do
		if mob.Parent and mob:FindFirstChild("Health") and mob:FindFirstChild("Progress") and mob.Health.Value > 0 then
			if not CollectionService:HasTag(mob, "Hidden") or (CollectionService:HasTag(mob, "Hidden") and tower:GetAttribute("HiddenDetection") == true) then
				local dist = (position - HRP.Position).Magnitude
				if dist <= range then
					table.insert(candidates, {
						mob = mob,
						dist = dist,
						progress = mob.Progress.Value,
						health = mob.Health.Value
					})
				end
			end
		end
	end

	if #candidates == 0 then
		return nil
	end

	-- Sorting logic
	table.sort(candidates, function(a, b)
		if mode == "Closest" then return a.dist < b.dist
		elseif mode == "Farthest" then return a.dist > b.dist
		elseif mode == "First" then return a.progress > b.progress
		elseif mode == "Last" then return a.progress < b.progress
		elseif mode == "Strongest" then return a.health > b.health
		elseif mode == "Weakest" then return a.health < b.health
		else return a.dist < b.dist end
	end)

	if mode == "Random" then
		return candidates[math.random(1, #candidates)].mob
	end

	return candidates[1].mob
end

-- Robust tower setup
function Towers.SetupTower(tower)
	-- Validate tower
	if not tower.PrimaryPart or not tower:FindFirstChild("Config") then
		tower:Destroy()
		return
	end

	if tower.PrimaryPart then
		tower.PrimaryPart.Anchored = true
	else
		tower:WaitForChild("HumanoidRootPart").Anchored = true
	end

	local connections = {}
	local module = require(tower.Config.Module.Value)
	local Config = tower.Config

	-- Cleanup function
	local function cleanUp()
		for _, c in ipairs(connections) do
			c:Disconnect()
		end
		targetCache[tower] = nil
	end

	-- Track destruction
	local destroyConn = tower.AncestryChanged:Connect(function(_, parent)
		if not parent then
			cleanUp()
		end
	end)
	table.insert(connections, destroyConn)

	-- Animation setup
	local idleTrack
	if Config.IdleAnimation then
		idleTrack = Animations.AnimateHumanoid(Config.IdleAnimation, tower, true, 1, Enum.AnimationPriority.Idle)
	end
	
	-- CACHE MOTOR6Ds once, before the while‑loop
	local leftShoulder  = tower:FindFirstChild("Left Shoulder", true)
	local rightShoulder = tower:FindFirstChild("Right Shoulder", true)

	local function aimArms(targetPos)
		if not (leftShoulder and rightShoulder) then return end
		local hrp = tower:FindFirstChild("HumanoidRootPart")
		if not hrp then return end

		local dir = (targetPos - hrp.Position).Unit
		local yaw = math.atan2(dir.X, dir.Z)   -- horizontal angle

		leftShoulder.C0  = leftShoulder.C0  * CFrame.Angles(0, yaw, 0)
		rightShoulder.C0 = rightShoulder.C0 * CFrame.Angles(0, yaw, 0)
	end

	-- Main tower loop
	while tower:IsDescendantOf(workspace) do

		if CollectionService:HasTag(tower, "Stunned") then task.wait(0.1) continue end

		local target = GetNextTarget(tower)
		if not target or not target.Parent or not target:FindFirstChild("Health") or target.Health.Value <= 0 then
			task.wait(0.1)
			continue
		end

		local targetPos = target.Position

		task.wait(tower:GetAttribute("Cooldown"))

		if CollectionService:HasTag(tower, "Stunned") then task.wait(0.1) continue end

		-- Separate body and aiming rotations
		if tower:FindFirstChild("HumanoidRootPart") and target then
			
			local HRP = tower:FindFirstChild("HumanoidRootPart")
			
			local leftShoulder = tower:FindFirstChild("Left Shoulder", true)
			local rightShoulder = tower:FindFirstChild("Right Shoulder", true)

			local originalLeftC0  = leftShoulder and leftShoulder.C0
			local originalRightC0 = rightShoulder and rightShoulder.C0

			-- 1) Yaw-only body rotation
			local hrp = tower:FindFirstChild("HumanoidRootPart")
			if hrp and target then
				local fromPos = hrp.Position
				local toPos   = Vector3.new(target.Position.X, fromPos.Y, target.Position.Z)
				local dir = (toPos - fromPos)
				if dir.Magnitude > 0 then
					hrp.CFrame = CFrame.new(fromPos, fromPos + dir.Unit)
				end
			end

			aimArms(targetPos)

			local shootPosition
			if Config:FindFirstChild("ShootPosition").Value ~= nil then
				shootPosition = Config:FindFirstChild("ShootPosition").Value.Position
			else
				shootPosition = tower:FindFirstChild("HumanoidRootPart") and tower:FindFirstChild("HumanoidRootPart").Position or tower.PrimaryPart.Position
			end

			if tower:FindFirstChild("HumanoidRootPart"):FindFirstChild("ShootSound") then
				tower.HumanoidRootPart.ShootSound:Play()
			end

			if Config.AttackAnimation then
				Animations.AnimateHumanoid(Config.AttackAnimation, tower, false, 1, Enum.AnimationPriority.Action, tower:GetAttribute("ProjectileType"), targetPos)
			end

			if target and target:FindFirstChild("Health") and target.Health.Value > 0 then
				module.Attack(tower, shootPosition, target, tower:GetAttribute("Damage"))
			end
		end
	end

	task.wait(0.1)
end

return Towers

Note: this is not finished at all parts and may contain code that doesn’t do anything.

2 Likes