Help me untangle this messy "battle royale" type script

  • What does the code do and what are you not satisfied with?
    So, I currently have a wizard game, and on one of the boss battles you have like a barrier, just like in fortnite. And I want to have a code, that both makes the barrier smaller and makes the plaeyer loose health when the player is outside the barrier.

So the problem is, that, it is working - most of the times, but like, every 10 or 20th time it fails. Often I get the error: script timeout exhausted allowed execution time. I believe it is because, I have a for loop inside a while loop. But, dear god. I am lost.

  • What potential improvements have you considered?
    Well, it must (I think) probably be something with the part where I constantly check if the player is outside the zone in order to take damage, there is something in that loop.
  • How (specifically) do you want to improve the code?
    I basically want it to work bug free, right now. It is very hacky and doesnt work a lot of times, and I know for a fact that it can be done better. It is just out of my imagiantion so to speak. I just, want a simple “battle royale” sort of thing, allthough that is not what I am making, it is kind of the same principle. Make the barrier smaller, and take damage if you are outside of that area. Yeah.

startBarrierShrink = game.Workspace.MainScripts.BarrierShrink.StartBarrierShrink


BARRIER = game.Workspace.Barrier

playersInGame = game.Workspace.PlayersInGame
serverStorage = game.ServerStorage
showRedScreenRemoteEvent = game.ReplicatedStorage.ShowRedScreen



function startBarrierShrink.OnInvoke(mapSpawned)

local damageLoopOn = true

centerOffMap = game.Workspace[mapSpawned].CenterOffMap

function makeSmaller1(startingPosition, endPosition)
   
	    for i = startingPosition,endPosition,-0.3 do
				if #playersInGame:GetChildren() > 1 then
					print("shrinking the boss battle barrier ")
		       		 BARRIER.Size = Vector3.new(i,308,i)
		      		  wait()
		
					else
						
					break
					
		        end
	    end
    
end

function makeSmaller2(startingPosition, endPosition)

    for i = startingPosition,endPosition,-0.3 do
		if #playersInGame:GetChildren() > 1 then
			
        BARRIER.Size = Vector3.new(i,308,i)
        wait()

		else
			
			break
			
        end
    end
    
end


function makeSmaller3(startingPosition, endPosition)
  
    for i = startingPosition,endPosition,-0.3 do
		if #playersInGame:GetChildren() > 1 then
			
        BARRIER.Size = Vector3.new(i,308,i)
        wait()

		else
			
			break
			
        end
    end
    
end

function makeSmaller4(startingPosition, endPosition)

    for i = startingPosition,endPosition,-0.3 do
		if #playersInGame:GetChildren() > 1 then
			
        BARRIER.Size = Vector3.new(i,308,i)
        wait()

		else
			
			break
			
        end
    end
    
end


function makeSmaller5(startingPosition, endPosition)

    for i = startingPosition,endPosition,-0.3 do
		if #playersInGame:GetChildren() > 1 then
			
        BARRIER.Size = Vector3.new(i,308,i)
        wait()

		else
			
			break
			
        end
    end
    
end



--[[
	The while loop below runs in a coroutine so it doesnt interfere with the rest of the script 
--]]


loopCheck = coroutine.create(function()
	
    while damageLoopOn do 


      for i, stringValuePlayer in ipairs(game.Workspace.PlayersInGame:GetChildren()) do
	
			
		player = game.Workspace:FindFirstChild(stringValuePlayer.Name)
		if player then --innex nil with "HumanoidRootPart"
		local hrp = player:WaitForChild("HumanoidRootPart")
		local positionFromMiddle = ((hrp.Position - centerOffMap.Position) * Vector3.new(1, 0, 1)).magnitude
		--playerGui = player:GetPlayerFromCharacter().PlayerGui DOESNT WORK 
		
				if positionFromMiddle > BARRIER.Size.X/2 and #playersInGame:GetChildren() > 1 then
				--playerGui.OutsideOfZone.Enabled = true --DOESNT WORK 
				
				print(positionFromMiddle .. " position from middle")
				print(BARRIER.Size.X/2 .. " barrier size")
			
				local playerNotChar = game.Players:GetPlayerFromCharacter(player)
				showRedScreenRemoteEvent:FireClient(playerNotChar)
				player:WaitForChild("Humanoid"):TakeDamage(math.random(10.5555555555,12.55555555)) 
				
			
		
				
					
						
				end
				
			

        end

  
    end
      wait(1)
end
end)

coroutine.resume(loopCheck)
print(centerOffMap:GetFullName()) ---this is where the script timeout error thingy happens. 



makeSmaller1(690,500)
wait(8)
makeSmaller2(500,420)
wait(math.random(20,25))

makeSmaller3(420, 350)
wait(15)

makeSmaller4(350, 280)
wait(math.random(10))

makeSmaller5(280, 1)

repeat 

wait(1)
until #playersInGame:GetChildren() == 0 


print("boss killed all wizard, or wizrds killed boss.")
--PROBABLY USE COROUTINE.YILED HERE OR SOMETHING!!
coroutine.yield(loopCheck)
damageLoopOn = false --makes sure the loop actually stops when no players. 
BARRIER.Size = Vector3.new(690, 308, 690)
return 
	
end


’
Guys, I will probably remake this post tomorrow. I just had to get it out, I have been sturggling with this script for 4 months. It will mean the world to me if someone could help. Thanks a lot lot lot.

(And yes, the script is awful. )

1 Like

Why do you have five functions that are the same? Just use one and call it multiple times. Also check this out: Battle Royale | Documentation - Roblox Creator Hub

1 Like

What’s the point of functions if they’re just repeated blocks of code, hardly any scripts have the same code. However by utilizing parameters functions become way more versiatle.


https://gyazo.com/fb02ca64fa3a6594cf15c9fc501e0c5f

Im assuming makeSmaller2 is not supposed to print whiie makeSmaller1 is supposed to?

If that’s the case have a parameter in the function named DEBUG_MODE. And when DEBUG_MODE is passed in as true in the function print statements are executed for eg:

if DEBUG_MODE == false then
print ‘bla bla’
end

And if the DEBUG_MODE Is nil (meaning it wasn’t specfied in the function argument make it default as false.

if DEBUG_MODE == nil then DEBUG_MODE == false end

1 Like

I executed your script (making small modifications) and everything worked fine. I couldn’t replicate the error. There doesn’t seem to be a problem of that kind either. Well, I’m not an advanced scripter, so I could be wrong
Baseplate2.rbxl (21.0 KB)
But I could tell you this, the coroutine.yield(loopCheck) will make the script yield, so the damageLoopOn = false will not be executed and your coroutine loopCheck will never die

1 Like

@DimbleGames
@luya_yobanka

Thanks, I agree - it’s stupid to have 5 loops for the same thing. I’ll change that.

But my problem is not really those functions, my problem is that the method I am using is not sustainable, hacky and buggy. The way I am checking if people are outside the barrier etc.

And I should probably add, that it is round-based. So it has to be repeated like every minute - and it should not damage people outside the zone if no players are in game. This is solved, allthough, the method again is probably bad.

Yeah, so the issue is that it once in a while doesn’t work and I get that error, script timeput etc…

But the issue is that the method (see the other reply I made) is not good.

And, the coroutine is supposed to stop when the round is over so it doesnt timeout the script. And then start the coroutine again when round is starting again.

I hope you get what I mean, and I am really gratefull that you took your time to test it.

Personally, I don’t think it’s about looking for a better method, because the best method is the one that works, and I think your method works. It is true that it can be optimized but the essence will not change. To clarify my answer keep this in mind: the coroutine.yield(loopCheck) does not allow loopCheck to die. And when you do startBarrierShrink.Invoke (somewhere in your game), it creates a new instance of loopCheck (which does not die) because you have defined the corourtine inside startBarrierShrink.OnInvoke. So there will be a lot of instances of loopCheck which is probably causing the timeput script error.

1 Like

So I should move the coroutine (and the while loop inside) outside of the invoke?

And that should maybe solve the issue?

probably. Anyway, it is better to create a single instance and resume it and make it yield all the time. but as I mentioned I am not an expert

Could you elaborate? I didn’t quite get you
Thanks

More expert than me, (at least on this field) lol

it could be something like this

local flag
local running -- for security purposes only

local coroutine1 = coroutine.create(function ()
	while true do
		running = true
		while flag do
			print("do something")
			wait() -- must be yielded in some form
		end
		running = false
		coroutine.yield() -- this will yield the while true loop
	end
end)

local bindable = Instance.new("BindableFunction")
bindable.OnInvoke = function ()
	local flag = true
	
	coroutine.resume(coroutine1)
	
	wait(1) -- some code
	
	local flag = false
	
end

-- somewhere

while running do
	wait()
end
bindable.Invoke()

Of course there is no recipe, so you should try what works for you.

1 Like

Thank you very much. I will test it iut first thing tomorrow! Thank you!

Hey sorry, I didn’t get to test it out today. I will do it tomorrow, see you around! :slight_smile:

Can I just ask why you have 2 while loops?

One while true do, and one while flag do.

And, the coroutine.yield() why is it inside the while true do loop, and not like, inside the coroutine?

Can you do this:

    local coroutine1 = coroutine.create(function ()
    	while true do --why is this here? or, what does it do?
    		running = true
    		while flag do --isn't this the only we need?
    			print("do something")
    			wait() -- must be yielded in some form (what do you mean by that?)
    		end
    		running = false
    		
    	end
    coroutine.yield()  --here instead? 
    end)

(I added some comments to the code)

Thank you very much for your help so far! I am really looking forward to your answer. And I made another code if you would like to see that also! I am just a little confusing where to put the coroutine.yield().

Thank you very much, yet again!

the while true loop serves to make the coroutine live through its entire game, if you don’t want that, you can create a new control variable, say canrun (while canrun do …) and modify the value of canrun somewhere in your script (or game).

The while flag loop is intended to act as your while damageLoopOn loop (sorry, I didn’t remember that name at the time, so just call it flag).

Well, since coroutine.yield() serves to temporarily suspend the execution of the coroutine, it should be put where it is convenient for its logic to work. Think of coroutine.yield() as if it were break, where would you put break? In this case, my intention was that coroutine.yield() would suspend the coroutine once some work was done, which would be when flag was false. Then, somewhere in your game resume the coroutine again.

wait() -- must be yielded in some form is very important. All coroutines have a timeout, if this time is exceeded you will get the Game script timeout error (just the one you received in the beginning). I don’t remember its value, but I’m sure that investigating a little you will get it. You should make sure that none of your coroutines break this time limit. In practice, there are only a few cases in which this time can be broken: when using infinite loops, when doing complex and deep recursion (or infinite), when doing intensive procedures (such as terrain manipulation), and others (which I do not know). In the case of infinite loops typically coroutine.yield() (as I use it) or wait() are used. wait() as it says in the documentation, temporarily suspends the coroutine resetting this time limit. This is even more complicated, but this is as far as my understanding goes.

1 Like

What do you mean with live through the entire game? I mean, I want it to only be on certain times, when the server fights a boss. Thank you for your response! :grinning:

And here, should I write: coroutine.yield(coroutine1)?

This is the script I have made so far: (will this work??)

damageOutsidePlayers = coroutine.create(function()
while gameActive do

		for i, playerNameObject in pairs(game.Workspace.PlayersInGame:GetChildren()) do 

			--pcall here?
			local characterModel = game.Workspace:FindFirstChild(playerNameObject.Name)

			if characterModel then  
				
				 local humanoidRootPart = characterModel:WaitForChild("HumanoidRootPart")
				 local positionFromCenterMap = (humanoidRootPart.Position - centerOfMap.Position * Vector3.new(1,0,1).magnitude)

				 if positionFromCenterMap > BarrierObject.Size.X/2 and #playersCurrentlyPlaying:GetChildren() > 1 then 

				 local player = game.Players:GetPlayerFromCharacter(characterModel)
				 damageGUIEvent:FireClient(player)
				 characterModel:WaitForChild("Humanoid"):TakeDamage(math.random(10.555555,12.555555))

				 end


			end
		

		end

	end

coroutine.yield(damageOutsidePlayers)
end)

with some modifications your code works. With this you will understand better what I wanted to say before.
Baseplate3.rbxl (21.3 KB)

Here is the detailed explanation of how the resume-yield pair works. What coroutine.yield(coroutine1) does is to pass the reference of coroutine1 to the next resume which will return that value if that is the case. I’m sorry, it’s the best I can explain, even for me it’s a bit confusing.

2 Likes