If you dont want to go the route to while wait() check magnitude between enemy and tower then I would suggest having a physical part representing the range for the tower and having a .touched event and checking if enemy touched and then while wait() and check magnitude, if no longer in range then break the loop.
both seem bad, I would ultimately suggest trying to utilize zoneplus v3.x module it seems to have something effective if you plan to have a physical part representing the range of a tower but requires basepart or etc to work for the enemies:
trackItem
--where characterOrBasePart is the determined physical range part, zone is the zone module.
zone:trackItem(characterOrBasePart)
This is used to detect your own custom instances within zones, such as NPCs, and is a recommended replacement for part-events/methods.
An item can be any BasePart or Character/NPC (i.e. a model with a Humanoid and HumanoidRootPart). Once tracked, it can be listened for with the zone.itemEntered and zone.itemExited events.
An item will be automatically untracked if destroyed or has its parent set to nil.
--can be untracked manually
I actually just noticed you have the zone module already lol
with zoneplus in mind, youll have to determine how to identify and specify each unique enemy that comes and goes within the range zone and not duplicate. I think the best way would be to have a table of in-range enemies:
--strongly reccommended to have a singular basepart to prevent range errors i.e. part leaves but enemy still in range barely by other parts.
EnemiesTargetable = {}
--you need only to trackitem when you spawn in a new enemy! anything else not being tracked will not be identified.
--utilizing zone.itemEntered:
zone.itemEntered:Connect(function(BasePart)
--parent crucial because there can be multiple,
--rely on parents of basepart to prevent duplicates.
if not table.find(EnemiesTargetable, BasePart.Parent) then
table.insert(EnemiesTargetable, BasePart.Parent)
end
end)
--inversely for itemexitted
zone.itemExited:Connect(function(BasePart)
--parent crucial because there can be multiple baseparts associated with a parent,
--rely on parents of basepart to prevent duplicates or errors.
--we can also assume that this is a queue type system,
--so theoretically we can always do: table.remove(EnemiesTargetable, 1)
--due to first in line is always the first to exit yes? Otherwise the following will work:
if table.find(EnemiesTargetable, BasePart.Parent) then
for i=1, #EnemiesTargetable, 1 do
if EnemiesTargetable[i]==BasePart.Parent then
table.remove(EnemiesTargetable, i)
end
end
end
end)
and then obviously, you will need to come up with when target==nil then attack the first target in enemies targetable table, you also need to implement when table.remove, targetbool = nil.
Although I am very confused on why you have a deal damage event..?