What are some good ways to deal lingering AOE damage to players?

I’ve tried a couple of different methods to create fire, where when a player walks into a certain area, they repeatedly take damage until they either die or leave the AOE. These “fires” can be created by the use of certain items. As a result, many instances can be occurring at the same time. What are some solid ways I could deal a constant, repeating damage, from multiple instances, with minimal impact on the server?

An old method of mine was to create an attachment at the center point of the fire, and in a while wait(1) do loop, go through each player and check their magnitude. If a player is under a certain threshold, the script deals damage to them. This is simple and works well, up until there are a handful of instances at once. The “fires” are not permanent, I should add. They only loop for so long before being destroyed. If enough fire instances are being run at once, the server slows to a halt.

A method I tried more recently was to have a script inserted in each player as their character loads in. Those scripts continuously check if they are within a certain distance to any fire instances. If they are, repeatedly deal damage until they are not. The fire instances themselves have no scripting with this method, and simply create markers for the character script to detect as fires. This method actually caused more lag.

While writing this, I thought about having one single script constantly check through any fire instances and go through the player list, dealing damage accordingly. Perhaps all the different scripts all running at once are what is causing lag. I’ll give this method a try, but what would be my best course of action, and does anyone have any experience with lingering AOE damage or fires?

1 Like

If I understand correctly, you have parts/instances that deal damage over time. Then you want to constantly check if the player is on multiple of said parts. If this is the case you’re on the right track of having one script to run this. What you could do is create a variable that hold the number of parts the player is currently on. So If I’m on two parts the variable equals two. Then use this variable to deal damage to then accordingly.

1 Like

If you really want to efficiently check if something’s “in range” of something else, use this fast distance comparison algorithm where you don’t have an expensive square root call (.Magnitude).

tl;dr: With regards to the distance formula, a^2+b^2=c^2, you don’t have to calculate c when comparing distances. You can just square the other distance you’re comparing to in order to avoid an expensive square root call.

You absolutely want to use only one script if the effect is the same across many scripts. Keep track of the fire centers using a table and iterate on that. If overlapping fire radii doesn’t matter to you (“I am in any fire => I’m taking the same amount of damage”)

Finally…avoid while wait(...) do constructs. They’re are almost always better served by connecting to RunService.Stepped, because it more strictly adheres to the Roblox task scheduler loop, and you can more accurately apply DPS, as while wait(...) do discards the actual time waited, which you need.

5 Likes

I’ve finished the third method, and it’s working pretty well for having many fires all burning at once. I’ve connected the RunService.Heartbeat event to the function instead of the while wait() loop as you suggested, Ozzypig, though I do not understand the difference being made here.

while wait(1) do __ end
would work the same way as

local x = 0
RunService.HeartBeat:Connect(function()
   x = x + 1
   if x % 60 then __ end
end)
  1. The way you wrote that code would have the same issues as while wait(1) do. @Ozzypig meant that you should capture the actual time change, since neither wait nor Heartbeat are perfect timers:

    RunService.Heartbeat:Connect(function(timeWaited)
        x = x + timeWaited
        if x >= 60 then
            -- scale damage based on actual time, rather than
            -- pretending you are at exactly 60 seconds
            x = 0
        end
    end)
    

    Although, to be fair, you could do that with wait as well, since it actually does return the time it actually waited:

    while true do
        x = x + wait(1)
        if x >= 60 then
            -- scale damage based on actual time
            x = 0
        end
    end
    
  2. A better reason is that wait can be a very bad timer indeed – in some cases, it will wait for much longer than the seconds you gave it. Heartbeat is going to be more consistent and you can check the time elapsed more accurately.

  3. Semi-related, but you should also think about the consequences of looping through all players at each second. That means that there’s like this global timer happening. Could lead to inconsistencies, since it’s not based on when the player entered the fire.

    For instance, if I stay in the flames for 2.5 seconds, but I entered them right after the last damage check occurred, I’ll get hit twice.

    Another player stays in the flames for 2.5 seconds too, but enters them right before the next damage occurs. They’ll get hit three times, not two.

    That might not be an issue for your game, but just something to keep in mind.

4 Likes

In your example, you are counting every 60 frames. Under normal conditions this might be fine, but you can’t guarantee 60 frames will happen the exact same amount of time.

By using the amount of time that has passed since the last frame, or measuring the current game time (Workspace.DistributedGameTime, for example), you can accurately keep time for stuff like DPS effects. In such cases, values should not be affected by the number of frames per unit time (something controlled by Roblox’s internals - not your game). After all, it’s damage per second, not damage per frame.

Finally, use Stepped not Heartbeat. It’s more appropriate here for a variety of reasons. The Task Scheduler article I linked should answer your questions there

1 Like