Capture/Control point review


#1
		local capturer

		local flagColor = flag.BrickColor
		
		local capturers = 0
		defenders = 0
		
		flag.Transparency = 0
		
		for _, player in pairs(players:GetPlayers()) do
			local character = player.Character
			if not character then return end
			
			local humanoidRootPart = character:FindFirstChild('HumanoidRootPart')
			if not humanoidRootPart then return end
			
			local humanoid = character:FindFirstChild('Humanoid')
			local distance = (humanoidRootPart.CFrame.p - flag.CFrame.p).magnitude
			
			if distance < range and humanoid.Health > 0 then
				if firstColor == nil then
					firstColor = player.TeamColor
				end
				if firstColor ~= nil then
					if player.TeamColor == firstColor then
						if player.TeamColor ~= flagColor then 
							capturers = capturers + 1
							giveCapturing(player)
						end
					else
						defenders = defenders + 1
						giveCapturing(player)
					end
				end
				capturer = player
			else
				giveCapturing(player)
			end
		end
		
		if capturers > 0 then
			if defenders == 0 then
				
				flag.Transparency = 0
				local ratio = 2.5
				
				if not reset then
					if percent < 100 then
						percent = percent + (capturers/ratio)
					else
						percent = 100
						reset = true
					end
				elseif reset then
					if percent > 0 then
						percent = percent - (capturers/ratio)
					else
						
						local color = flag.BrickColor
						percent = 0
						reset = false
						flag.BrickColor = firstColor
						firstColor = nil
						
						for _, player in pairs(players:GetPlayers()) do
							if player.TeamColor == flag.BrickColor then
								local character = player.Character
								if not character then return end
								
								local humanoidRootPart = character:FindFirstChild('HumanoidRootPart')
								if not humanoidRootPart then return end
								
								local humanoid = character:FindFirstChild('Humanoid')
								local distance = (humanoidRootPart.CFrame.p - flag.CFrame.p).magnitude
									
								if player:FindFirstChild('Capturing') then
									local capturing = player.Capturing
									debris:AddItem(capturing, 1)
								end
								if distance < range and humanoid.Health > 0 then
									print(1)
									-- Players who have captured it
								end
							end
						end
					end
				end
			else
				flag.Transparency = 0.5
			end
		else
			firstColor = nil
			if percent > 0 then
				percent = percent - 1
				reset = false
			else
				reset = false
			end
		end
		flag.CFrame = defaultCFrame - Vector3.new(0, (percent/100)*(pole.Size.Y - flag.Size.Y), 0)
	
		if configuration.BLUE_SCORE == configuration.KOTH_SCORE or configuration.RED_SCORE == configuration.KOTH_SCORE then
			timeManager:End()
		end
	end

So it’s basically a flag that players stand near to claim for their team. This is NOT my own code!! It’s taken from a really old copy of one of the original Paintball games. I have made small edits around the place to get it to work, but since this is an old script (around 2013-2014) I was wondering how I could go about making this a LOT cleaner and easier to read. I was thinking of firstly just chucking all the variables in a Module somewhere in RepStorage, so I can easily change the certain values around without having to find the script in multiple maps and change it all.

Trying to break it down to the bare bones would be like

  • If player gets within a certain range of the flag and they are still alive (no dead body parts flying around the map) then the flag starts to lower and become ‘captured’
  • If somebody else from your team comes within the capture range, then it speeds up the capture rate.
  • If a player from the opposing team enters while you are trying to capture, the flag will stop, and won’t move until one the players has left the area.
  • If 2 red players and 1 blue player are in the area, the flag will go down for the red team.
  • Once flag has gone to the bottom of the pole and then back up to the top, it has become captured and changes color to show what team currently has the flag ‘captured’

Not sure if I am missing any points, but that’s kinda the general breakdown. I’m sure I could probably use functions a lot more too? Like checkPlayerDistance() checkIfAlive() checkDefenders(), or something like that etc. to kinda break down the code a lil more


split this topic #2

A post was merged into an existing topic: Off-topic and bump posts


#3

Personally I don’t find anything about that code difficult to understand? There’s a small amount of repetition what with the two Player for loops, but without knowing how this code is called (you cut off the first line of what I assume is a function) I can’t make any suggestions for that.

Your only issue with this seems to be wanting to modify it in multiple places at once, and moving the configuration to a shared ModuleScript solves that neatly.

If you want to refactor this, I suggest having all of the flags on the currently active map controlled from a single script rather than a separate script for each flag.


#4

Ok, yea each flag will be controlled by a single script (this script however only controls 1, because there’s only 1 flag in the game mode)

I’ve done some editing since the OG post, and I have this now, which works and all, but having 2 while loops seems really bad and not entirely sure how to fix it. It’s incredibly long :confused:

function kingOfTheHill:Gamemode()
	
	local map = mapHolder:WaitForChild('Map')
	local kingFlag = map:WaitForChild('KingFlag')
	
	local flag = kingFlag:WaitForChild('Flag')
	local pole = kingFlag:WaitForChild('Pole')
	
	local percent = 0
	local range = 15
	
	local defaultCFrame = flag.CFrame
	
	local paused = false
	local reset = false
	
	local firstColor = nil
	
	configuration.BLUE_SCORE = 0
	configuration.RED_SCORE = 0
	
	updateScore:FireAllClients(configuration.BLUE_SCORE, configuration.RED_SCORE)
		
	local start = coroutine.create(function()
		print('new KOTH')
		while configuration.GAME_RUNNING do
			
			local capturer = nil
	
			local flagColor = flag.BrickColor
			
			local capturers = 0
			local defenders = 0
			
			flag.Transparency = 0
			
			for _, player in pairs(players:GetPlayers()) do
				local character = player.Character
				if not character then return end
				
				local humanoidRootPart = character.PrimaryPart
				if not humanoidRootPart then return end
				
				local humanoid = character:FindFirstChildWhichIsA('Humanoid')
				local distance = (humanoidRootPart.CFrame.Position - flag.CFrame.Position).Magnitude
				
				if distance < range and humanoid.Health > 0 then
					if firstColor == nil then
						firstColor = player.TeamColor
					end
					if firstColor ~= nil then
						if player.TeamColor == firstColor then
							if player.TeamColor ~= flagColor then 
								capturers = capturers + 1
								giveCapturing(player, flag)
							end
						else
							defenders = defenders + 1
							giveCapturing(player, flag)
						end
					end
					capturer = player
				else
					giveCapturing(player, flag)
				end
			end
			
			if capturers > 0 then
				if defenders == 0 then
					
					flag.Transparency = 0
					local ratio = 2.5
					
					if not reset then
						if percent < 100 then
							percent = percent + (capturers/ratio)
						else
							percent = 100
							reset = true
						end
					elseif reset then
						if percent > 0 then
							percent = percent - (capturers/ratio)
						else
							
							local color = flag.BrickColor
							percent = 0
							reset = false
							flag.BrickColor = firstColor
							firstColor = nil
							
							for _, player in pairs(players:GetPlayers()) do
								if player.TeamColor == flag.BrickColor then
									local character = player.Character
									if not character then return end
									
									local humanoidRootPart = character.PrimaryPart
									if not humanoidRootPart then return end
									
									local humanoid = character:FindFirstChildWhichIsA('Humanoid')
									local distance = (humanoidRootPart.CFrame.Position - flag.CFrame.Position).Magnitude
										
									if player:FindFirstChild('Capturing') then
										local capturing = player.Capturing
										debris:AddItem(capturing, 1)
									end
									if distance < range and humanoid.Health > 0 then
										updateExp:Fire(player, amounts.Exp.CaptureKOTH, true)
										updateGold:Fire(player, amounts.Gold.CaptureKOTH, true)
									end
								end
							end
						end
					end
				else
					flag.Transparency = 0.5
				end
			else
				firstColor = nil
				if percent > 0 then
					percent = percent - 1
					reset = false
				else
					reset = false
				end
			end
			flag.CFrame = defaultCFrame - Vector3.new(0, (percent/100)*(pole.Size.Y - flag.Size.Y), 0)
		
			if configuration.BLUE_SCORE == configuration.KOTH_SCORE or configuration.RED_SCORE == configuration.KOTH_SCORE then
				timeManager:End()
				break
			end
			wait()
		end
	end)
	
	local setScore = coroutine.create(function()
		while configuration.GAME_RUNNING do
			if flag.BrickColor ~= BrickColor.new('Ghost grey') then
				print(flag.BrickColor)
				if flag.BrickColor == teams.Blue.TeamColor then
					configuration.BLUE_SCORE = configuration.BLUE_SCORE + 1
				else
					configuration.RED_SCORE = configuration.RED_SCORE + 1
				end
				
				updateScore:FireAllClients(configuration.BLUE_SCORE, configuration.RED_SCORE)
			end
			wait(2)
		end
	end)

	coroutine.resume(start)

	coroutine.resume(setScore)	
	
	timeManager:StartTimer(configuration.KOTH_TIME, true)
end

Mostly just looking at what’s inside the start and setScore courotuine. If the setScore could be changed in anyway? All it’s really doing is giving whichever team owns the flag a point every 2 seconds


#5

You can avoid using two loops here by keeping track of the time delta between each iteration and only adding score or updating defenders when enough time has elapsed.

A skeleton of that would be something like this:
local timeSinceScore = 0
local scoreUpdateInterval = 2

local timeSinceCaptureUpdate = 0
local captureUpdateInterval = 0.5

local last = tick()
while running do
	local t, dt = tick()
	dt, last = t - last, t -- can't local on this line or `last` is captured!

	timeSinceCaptureUpdate = timeSinceCaptureUpdate + dt
	if timeSinceCaptureUpdate >= captureUpdateInterval then
		timeSinceCaptureUpdate = timeSinceCaptureUpdate - captureUpdateInterval
		-- calculate capturerers and defenders, move flag, etc.
	end

	timeSinceScore = timeSinceScore + dt
	if timeSinceScore >= scoreUpdateInterval then
		timeSinceScore = timeSinceScore - scoreUpdateInterval
		-- give score to team that owns flag
	end

    wait() -- Or RenderStepped, Heartbeat, etc.
end

If you wanted to update the flag’s position smoothly/continously, you can use this same dt variable along with some rates to modify the “capture percentage” rather than stepping it a fixed amount each update.

E.g. Assuming ratio to be a “capture efficiency” modifier, something like this could work:

local captureRate = 5 -- percent per second

percent = math.min(100, math.max(0, percent + captureRate*dt*ratio))
-- max and min used here for clamping

EDIT: P.S. if you only have two teams in this KOTH you might consider having a single “capturers” value where negative count would mean more defenders than capturers and positive the opposite. Zero would mean nobody is around or the presence of both teams is equal.