Glitch in upgrade system (tower defense game)

Hi im creating a Tower Defense game but upgrade system is glitched how i can fix?
error:


Tower module script:

local PhysicsService = game:GetService("PhysicsService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")


local functions = ReplicatedStorage:WaitForChild("Functions")
local requestTowerFunction = functions:WaitForChild("RequestTower")
local sellTowerFunction = functions:WaitForChild("SellTower")
local spawnTowerFunction = functions:WaitForChild("SpawnTower")
local changeModeFunction = functions:WaitForChild("ChangeTowerMode")


local map = workspace.Grassland
local Events = ReplicatedStorage:WaitForChild("Events")
local animateTowerEvent = Events:WaitForChild("AnimateTower")


local maxTowers = 10
local tower = {}



function tower.FindTarget(newTower, range, mode)
	local bestTarget = nil 
	local bestWaypoint = nil
	local bestDistance = nil
	local bestHealth = nil

	for i, mob in ipairs(workspace.Mobs:GetChildren()) do
		local distanceToMob = (mob.HumanoidRootPart.Position - newTower.HumanoidRootPart.Position).Magnitude
		local distanceToWaypoint = (mob.HumanoidRootPart.Position - map.Waypoints[mob.MovingTo.Value].Position).Magnitude 

		if distanceToMob <= range then
			if mode == "Near" then
				range = distanceToMob
				bestTarget = mob
			elseif mode == "First" then
				if not bestWaypoint or mob.MovingTo.Value >= bestWaypoint then
					bestWaypoint = mob.MovingTo.Value

					if not bestDistance or distanceToWaypoint < bestDistance then
						bestDistance = distanceToWaypoint
						bestTarget = mob
					end
				end
			elseif mode == "Last" then
				if not bestWaypoint or mob.MovingTo.Value <= bestWaypoint then
					bestWaypoint = mob.MovingTo.Value

					if not bestDistance or distanceToWaypoint > bestDistance then
						bestDistance = distanceToWaypoint
						bestTarget = mob

					end
				end

			elseif mode == "Strong" then
				if not bestHealth or mob.Humanoid.Health > bestHealth then
					bestHealth = mob.Humanoid.Health
					bestTarget = mob 

				end
			elseif mode == "Weak" then
				if not bestHealth or mob.Humanoid.Health < bestHealth then
					bestHealth = mob.Humanoid.Health
					bestTarget = mob 
				end
			end
		end
	end
	return bestTarget
end


function tower.Attack(newTower, player)
	local config = newTower.Config
	local target = tower.FindTarget(newTower, config.Range.Value, config.TargetMode.Value)
	if target and target:FindFirstChild("Humanoid") and target.Humanoid.Health > 0 then


		local targetCFrame = CFrame.lookAt(newTower.HumanoidRootPart.Position, target.HumanoidRootPart.Position)
		newTower.HumanoidRootPart.BodyGyro.CFrame = targetCFrame

		animateTowerEvent:FireAllClients(newTower, "Attack")

		target.Humanoid:TakeDamage(config.Damage.Value)

		if target.Humanoid.Health <= 0 then
			player.Gold.Value += target.Humanoid.MaxHealth
		end

		task.wait(config.Cooldown.Value)
	end
	task.wait(0.1)

	if newTower and newTower.Parent then
		tower.Attack(newTower, player)
	end
end

function tower.ChangeMode(player, model)
	if model and model:FindFirstChild("Config") then
		local targetMode = model.Config.TargetMode
		local modes = {"First", "Last", "Near", "Strong", "Weak"}

		local modeIndex = table.find(modes, targetMode.Value)

		if modeIndex < #modes then
			targetMode.Value = modes[modeIndex + 1]

		else
			targetMode.Value = modes[1]
		end

		return true
	else
		warn("Unable to change tower mode")
		return false
	end
end

changeModeFunction.OnServerInvoke = tower.ChangeMode


function tower.Sell(player, model)
	if model and model:FindFirstChild("Config") then
		if model and model:FindFirstChild("Config") then
			player.PlacedTowers.Value -= 1
			player.Gold.Value += model.Config.Price.Value
			model:Destroy()
			return true
		end
	end
	warn("Unable to sell this tower")
	return false
end
sellTowerFunction.OnServerInvoke = tower.Sell


function tower.Spawn(player, name, cframe, previous)
	local allowedToSpawn = tower.CheckSpawn(player, name)
	local oldMode = nil

	if allowedToSpawn then

		local newTower
		if previous then
			oldMode = previous:WaitForChild("Config").TargetMode.Value
			previous:Destroy()
			newTower = ReplicatedStorage:WaitForChild("Towers").Upgrades[name]:Clone()
		else
			newTower = ReplicatedStorage.Towers[name]:Clone()
			player.PlacedTowers.Value += 1
		end

		local ownerValue = Instance.new("StringValue") 
		ownerValue.Name = "Owner" 
		ownerValue.Value = player.Name
		ownerValue.Parent = newTower.Config 

		local targetMode = Instance.new("StringValue") 
		targetMode.Name = "TargetMode"
		targetMode.Value = oldMode or "First" 
		targetMode.Parent = newTower.Config


		newTower.HumanoidRootPart.CFrame = cframe
		newTower.Parent = workspace.Towers
		newTower.HumanoidRootPart:SetNetworkOwner(nil)

		local bodyGyro = Instance.new("BodyGyro")
		bodyGyro.MaxTorque = Vector3.new(math.huge, math.huge, math.huge)
		bodyGyro.D = 0
		bodyGyro.CFrame = newTower.HumanoidRootPart.CFrame
		bodyGyro.Parent = newTower.HumanoidRootPart

		for i, object in ipairs(newTower:GetDescendants()) do
			if object:isA("BasePart") then
				PhysicsService:SetPartCollisionGroup(object, "Tower")
			end
		end

		player.Gold.Value -= newTower.Config.Price.Value


		coroutine.wrap(tower.Attack)(newTower, player)
		return newTower
	else
		warn("Requested tower does not exist:", name)
		return false
	end
end


spawnTowerFunction.OnServerInvoke = tower.Spawn

function tower.CheckSpawn(player, name, previous)
	local towerExists = ReplicatedStorage.Towers:FindFirstChild(name, true)

	if towerExists then
		if towerExists.Config.Price.Value <= player.Gold.Value then
			if previous or player.PlacedTowers.Value < maxTowers then
				return true
			else
				warn("Player was reached max limit")
			end
		else
			warn("Player cannot afford")
		end
	else
		warn("That tower does not exist")
	end

	return false

end
requestTowerFunction.OnServerInvoke = tower.CheckSpawn

return tower

Localscript:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local PhysicsService = game:GetService("PhysicsService")


local Modules = ReplicatedStorage:WaitForChild("Modules")
local health = require(Modules:WaitForChild("Health"))


local towers = ReplicatedStorage:WaitForChild("Towers")
local functions = ReplicatedStorage:WaitForChild("Functions")


local requestTowerFunction = functions:WaitForChild("RequestTower")
local spawnTowerFunction = functions:WaitForChild("SpawnTower")
local sellTowerFunction = functions:WaitForChild("SellTower")
local changeModeFunction = functions:WaitForChild("ChangeTowerMode") 


local gold = Players.LocalPlayer:WaitForChild("Gold")
local map = workspace:WaitForChild("Grassland")
local base = map:WaitForChild("Base")
local info = workspace:WaitForChild("Info")


local camera = workspace.CurrentCamera
local gui = script.Parent


local lastTouch = tick()
local canPlace = false
local towerToSpawn = nil
local hoveredInstance = nil
local selectedTower = nil
local Rotation = 0
local placedTowers = 0
local maxTowers = 10


local function SetupGui()
	health.Setup(base, gui.Info.Health)

	workspace.Mobs.ChildAdded:Connect(function(mob)
		health.Setup(mob)
	end)

	info.Message.Changed:Connect(function(change)
		gui.Info.Message.Text = change
		if change == "" then
			gui.Info.Message.Visible = false
		else
			gui.Info.Message.Visible = true
		end
	end)

	info.Wave.Changed:Connect(function(change)
		gui.Info.Stats.Wave.Text = "Wave:" .. change
	end)

	gold.Changed:Connect(function(change)
		gui.Info.Stats.Gold.Text = "$" .. gold.Value
	end)
	gui.Info.Stats.Gold.Text = "$" .. gold.Value
end

SetupGui()

local function MouseRaycast(model)

	local mousePosition = UserInputService:GetMouseLocation()
	local mouseRay = camera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	local raycastParams = RaycastParams.new()

	local blacklist = camera:GetChildren()
	table.insert(blacklist, model)

	raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
	raycastParams.FilterDescendantsInstances = blacklist

	local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParams)

	return raycastResult

end

local function CreateRangeCircle(tower, placeholder)

	local range = tower.Config.Range.Value
	local height = (tower.PrimaryPart.Size.Y / 2) + tower.Humanoid.HipHeight
	local offset = CFrame.new( 0, -height, 0)
	local p = Instance.new("Part")
	p.Name = "Range"
	p.Shape = Enum.PartType.Cylinder
	p.Material = Enum.Material.Neon
	p.Transparency = 0.9
	p.Size = Vector3.new(2, range * 2, range * 2)
	p.TopSurface = Enum.SurfaceType.Smooth
	p.BottomSurface = Enum.SurfaceType.Smooth
	p.CFrame = tower.PrimaryPart.CFrame * offset * CFrame.Angles(0, 0, math.rad(90))
	p.CanCollide = false
	p.Anchored = true
	p.Parent = workspace.Camera

	if placeholder then
		p.Anchored = false
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = p
		weld.Part1 = tower.PrimaryPart
		weld.Parent = p
		p.Parent = tower
	else
		p.Anchored = true
		p.Parent = workspace.Camera
	end

end

local function RemovePlaceholderTower()
	if towerToSpawn then
		towerToSpawn:Destroy()
		towerToSpawn = nil
		Rotation = 0
		gui.Controls.Visible = false
	end
end

local function AddPlaceholderTower(name)

	local towerExists = towers:FindFirstChild(name)
	if towerExists then
		RemovePlaceholderTower()
		towerToSpawn = towerExists:Clone()
		towerToSpawn.Parent = workspace


		CreateRangeCircle(towerToSpawn, true)

		for i, object in ipairs(towerToSpawn:GetDescendants()) do
			if object:isA("BasePart") then
				PhysicsService:SetPartCollisionGroup(object, "Tower")
				if object.Name ~= "Range" then
					object.Material = Enum.Material.ForceField
					object.Transparency = 0.3
				end
			end
		end


		gui.Controls.Visible = true
	end
end

local function ColorPlaceholderTower(color)
	for i, object in ipairs(towerToSpawn:GetDescendants()) do
		if object:isA("BasePart") then
			object.Color = color
		end	
	end
end

local function toggleTowerInfo()
	workspace.Camera:ClearAllChildren()
	gui.Towers.Title.Text = "Towers: " .. placedTowers .. "/" .. maxTowers

	if selectedTower then
		CreateRangeCircle(selectedTower)
		gui.Selection.Visible = true
		local config = selectedTower.Config
		gui.Selection.Stats.Damage.Value.Text = config.Damage.Value
		gui.Selection.Stats.Range.Value.Text = config.Range.Value
		gui.Selection.Stats.Cooldown.Value.Text = config.Cooldown.Value
		gui.Selection.Title.TowerName.Text = selectedTower.Name
		gui.Selection.Title.TowerImage.Image = config.Image.Texture
		gui.Selection.Title.OwnerName.Text  = config.Owner.Value .. "'s"

		local modes = {
			["First"] = "rgb(150, 150, 150)",
			["Last"] = "rgb(50, 50, 50)",
			["Near"] = "rgb(50, 150, 0)",
			["Strong"] = "rgb(200, 50, 50)",
			["Weak"] = "rgb(50, 100, 200)"
		}
		local color = modes[config.TargetMode.Value]
		gui.Selection.Action.Target.Title.Text = "Target: <font color=\"" .. color .. "\">" .. config.TargetMode.Value .. "</font>"

		if config.Owner.Value == Players.LocalPlayer.Name then
			gui.Selection.Action.Visible = true
			local upgradeTower = config:FindFirstChild("Upgrade")
			if upgradeTower then
				gui.Selection.Action.Upgrade.Visible =  true
				gui.Selection.Action.Upgrade.Title.Text = "Upgrade (" ..upgradeTower.Value.Config.Price.Value .. ")"
			else
				gui.Selection.Action.Upgrade.Visible = false
			end
		else
			gui.Selection.Action.Visible = false 
		end

	else 
		gui.Selection.Visible = false
	end
end


local function SpawnNewTower()
	if canPlace then
		local placedTower = spawnTowerFunction:InvokeServer(towerToSpawn.Name, towerToSpawn.PrimaryPart.CFrame)
		if placedTower then
			placedTowers +=1
			selectedTower = placedTower
			RemovePlaceholderTower()
			toggleTowerInfo()
		end
	end
end

gui.Controls.Cancel.Activated:Connect(RemovePlaceholderTower)


gui.Selection.Action.Target.Activated:Connect(function()
	if selectedTower then
		local modeChangeSuccess = changeModeFunction:InvokeServer(selectedTower)
		if modeChangeSuccess then
			toggleTowerInfo()
		end
	end
end)

gui.Selection.Action.Sell.Activated:Connect(function()
	if selectedTower then
		local soldTower = sellTowerFunction:InvokeServer(selectedTower)

		if soldTower then
			selectedTower = nil
			placedTowers -= 1 
			toggleTowerInfo()
		end
	end
end)

gui.Selection.Action.Upgrade.Activated:Connect(function()
	if selectedTower then
		local upgradeTower = selectedTower.Config.Upgrade.Value
		local upgradeSuccess = spawnTowerFunction:InvokeServer(upgradeTower.Name, selectedTower.PrimaryPart.CFrame, selectedTower)

		if upgradeSuccess then
			selectedTower = upgradeSuccess
			toggleTowerInfo()
		end	
	end
end)


UserInputService.InputBegan:Connect(function(input, processed)
	if processed then
		return
	end

	if towerToSpawn then
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			SpawnNewTower()

		elseif input.UserInputType == Enum.UserInputType.Touch then
			local timeSinceLastTouch = tick() - lastTouch 
			if timeSinceLastTouch <= 0.25 then 
				SpawnNewTower()
			end
			lastTouch = tick()

		elseif input.KeyCode == Enum.KeyCode.R then
			Rotation += 45
		elseif input.KeyCode == Enum.KeyCode.X or input.KeyCode == Enum.KeyCode.C then
			RemovePlaceholderTower()
		end
	elseif hoveredInstance and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then
		local model = hoveredInstance:FindFirstAncestorOfClass("Model")
		if model and model.Parent == workspace.Towers then
			selectedTower = model
		else
			selectedTower = nil
		end
		toggleTowerInfo()
	end
end)

gui.Towers.Title.Text = "Towers:" .. placedTowers .. "/" .. maxTowers

for i, tower in pairs(towers:GetChildren()) do
	if tower:IsA("Model") then
		local button = gui.Towers.Template:Clone()
		local config = tower:WaitForChild("Config")
		button.Name = tower.Name
		button.Image= config.Image.Texture
		button.Visible = true
		button.Parent = gui.Towers
		button.Price.Text = config.Price.Value
		button.LayoutOrder = config.Price.Value
		button.Activated:Connect(function()
			local allowedToSpawn = requestTowerFunction:InvokeServer(tower.Name)
			if allowedToSpawn then
				AddPlaceholderTower(tower.Name)
			end
		end)
	end
end


RunService.RenderStepped:Connect(function()
	local result = MouseRaycast(towerToSpawn)
	if result and result.Instance then
		if towerToSpawn then
			hoveredInstance = nil
			if result.Instance.Parent.Name == "TowerArea" then
				canPlace = true
				ColorPlaceholderTower(Color3.new(0, 1, 0))
			else
				canPlace = false
				ColorPlaceholderTower(Color3.new(1, 0, 0))
			end
			local x = result.Position.X
			local y = result.Position.Y + towerToSpawn.Humanoid.HipHeight + (towerToSpawn.PrimaryPart.Size.Y / 2)
			local z = result.Position.Z

			local cframe = CFrame.new(x, y, z) * CFrame.Angles(0, math.rad(Rotation), 0)
			towerToSpawn:SetPrimaryPartCFrame(cframe)
		else
			hoveredInstance = result.Instance 
		end

	else
		hoveredInstance = nil
	end
end)

Why are you merely relying upon the name of the tower? wouldn’t there be duplicate names?

if previous or player.PlacedTowers.Value < maxTowers then
				return true
			else
				warn("Player was reached max limit")
			end

perhaps its triggering else because one other tower with the same name breaks initial condition.

local towerExists = ReplicatedStorage.Towers:FindFirstChild(name, true)

finds first child with name of Princess but there could be multiple princesses

1 Like