local d=20 local C=false local G={}G.__index=G local W={}W.__index=W W.ClassName="\083\105\103\110\097\108"function W.new()local d=setmetatable({},W)d._bindableEvent=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")d._argData=nil d._argCount=nil return d end function W.Fire(d,...)d._argData={...}d._argCount=select("\035",...)d._bindableEvent:Fire()d._argData=nil d._argCount=nil end function W.Connect(C,d)if not(type(d)=="\102\117\110\099\116\105\111\110")then error(("\099\111\110\110\101\099\116\040\037\115\041"):format(typeof(d)),2)end return C._bindableEvent.Event:Connect(function()d(unpack(C._argData,1,C._argCount))end)end function W.Wait(d)d._bindableEvent.Event:Wait()assert(d._argData,"\077\105\115\115\105\110\103 \097\114\103 \100\097\116\097\044 \108\105\107\101\108\121 \100\117\101 \116\111 \058\084\119\101\101\110\083\105\122\101\047\080\111\115\105\116\105\111\110 \099\111\114\114\117\112\116\105\110\103 \116\104\114\101\097\100\114\101\102\115\046")return unpack(d._argData,1,d._argCount)end function W.Disconnect(d)if d._bindableEvent then d._bindableEvent:Destroy()d._bindableEvent=nil end d._argData=nil d._argCount=nil end function G.new(q,Y)local D={}setmetatable(D,G)D.Group=q D.RegionHeight=Y or d D.DisplayBoundParts=C D.GroupParts={}D.Region=D:getRegion()D.PreviousPlayers={}D.PlayerAdded=W.new()D.PlayerRemoving=W.new()return D end function G.getRegion(d)local C={Min={},Max={}}for d,C in pairs(C)do C.Values={}function C.parseCheck(C,G)if d=="\077\105\110"then return C<=G elseif d=="\077\097\120"then return C>=G end end function C.parse(C,d)for d,G in pairs(d)do local W=C.Values[d]or G if C.parseCheck(G,W)then C.Values[d]=G end end end end for G,W in pairs(d.Group:GetDescendants())do if W:isA("\066\097\115\101\080\097\114\116")then table.insert(d.GroupParts,W)local G=W.Size*.5 local q={W.CFrame*CFrame.new(-G.X,-G.Y,-G.Z);W.CFrame*CFrame.new(-G.X,-G.Y,G.Z),W.CFrame*CFrame.new(-G.X,G.Y,-G.Z);W.CFrame*CFrame.new(-G.X,G.Y,G.Z),W.CFrame*CFrame.new(G.X,-G.Y,-G.Z);W.CFrame*CFrame.new(G.X,-G.Y,G.Z),W.CFrame*CFrame.new(G.X,G.Y,-G.Z);W.CFrame*CFrame.new(G.X,G.Y,G.Z)}for d,G in pairs(q)do local W,q,Y=G:GetComponents()local D={W,q,Y}C.Min:parse(D)C.Max:parse(D)end end end local G=Vector3.new(unpack(C.Min.Values))local W=Vector3.new(unpack(C.Max.Values))+Vector3.new(0,d.RegionHeight,0)if d.DisplayBoundParts then local C={BoundMin=G;BoundMax=W}for C,G in pairs(C)do local W=Instance.new("\080\097\114\116")W.Anchored=true W.CanCollide=false W.Transparency=.5 W.Size=Vector3.new(4,4,4)W.Color=Color3.fromRGB(255,0,0)W.CFrame=CFrame.new(G)W.Name=C W.Parent=d.Group end end local q=Region3.new(G,W)return q end function G.getPlayersInRegion(d)local C=Players:GetPlayers()local G={}for d,C in pairs(C)do local W=C.Character local q=W and W:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if q then table.insert(G,q)end end local W=workspace:FindPartsInRegion3WithWhiteList(d.Region,G,#C)local q={}local Y={}if#W>0 then for d,C in pairs(W)do local G=C.Parent if not q[G]then q[G]=true local d=Players:GetPlayerFromCharacter(G)if d then Y[d]=true end end end end return Y end function G.getPlayer(C,d)local G=d.Character local W=G and G:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if W then local d=W.Position+Vector3.new(0,4,0)local G=d+Vector3.new(0,-1,0)local q=Ray.new(d,(G-d).unit*C.RegionHeight)local Y=workspace:FindPartOnRayWithWhitelist(q,C.GroupParts)if Y then return true end end end function G.getPlayers(d)local C=d:getPlayersInRegion()local G={}for C,W in pairs(C)do if d:getPlayer(W)then if not d.PreviousPlayers[W]then d.PlayerAdded:Fire(W)end table.insert(G,W)elseif d.PreviousPlayers[W]then d.PlayerRemoving:Fire(W)end end d.PreviousPlayers=G return G end function G.initLoop(C,d)d=tonumber(d)or.5 local G=HttpService:GenerateGUID(false)C.currentLoop=G if not C.loopInitialized then C.loopInitialized=true;(coroutine.wrap(function()while C.currentLoop==G do wait(d)C:getPlayers()end end))()end end function G.endLoop(d)d.currentLoop=nil end return G
-- CONFIG
local defaultRegionHeight = 20
local displayBoundParts = false

local Zone = {}
Zone.__index = Zone

local Signal = {}
Signal.__index = Signal
Signal.ClassName = "Signal"

function Signal.new()
	local self = setmetatable({}, Signal)

	self._bindableEvent = Instance.new("BindableEvent")
	self._argData = nil
	self._argCount = nil -- Prevent edge case of :Fire("A", nil) --> "A" instead of "A", nil

	return self
end

function Signal:Fire(...)
	self._argData = { ... }
	self._argCount = select("#", ...)
	self._bindableEvent:Fire()
	self._argData = nil
	self._argCount = nil
end

function Signal:Connect(handler)
	if not (type(handler) == "function") then
		error(("connect(%s)"):format(typeof(handler)), 2)
	end

	return self._bindableEvent.Event:Connect(function()
		handler(unpack(self._argData, 1, self._argCount))
	end)
end

function Signal:Wait()
	self._bindableEvent.Event:Wait()
	assert(self._argData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
	return unpack(self._argData, 1, self._argCount)
end

function Signal:Disconnect()
	if self._bindableEvent then
		self._bindableEvent:Destroy()
		self._bindableEvent = nil
	end

	self._argData = nil
	self._argCount = nil
end

function Zone.new(group, regionHeight)
	local self = {}
	setmetatable(self, Zone)

	self.Group = group
	self.RegionHeight = regionHeight or defaultRegionHeight
	self.DisplayBoundParts = displayBoundParts
	self.GroupParts = {}
	self.Region = self:getRegion()
	self.PreviousPlayers = {}
	self.PlayerAdded = Signal.new()
	self.PlayerRemoving = Signal.new()

	return self
end

function Zone:getRegion()
	local bounds = { ["Min"] = {}, ["Max"] = {} }
	for boundType, details in pairs(bounds) do
		details.Values = {}
		function details.parseCheck(v, currentValue)
			if boundType == "Min" then
				return (v <= currentValue)
			elseif boundType == "Max" then
				return (v >= currentValue)
			end
		end
		function details:parse(valuesToParse)
			for i, v in pairs(valuesToParse) do
				local currentValue = self.Values[i] or v
				if self.parseCheck(v, currentValue) then
					self.Values[i] = v
				end
			end
		end
	end
	for _, part in pairs(self.Group:GetDescendants()) do
		if part:isA("BasePart") then
			table.insert(self.GroupParts, part)
			local sizeHalf = part.Size * 0.5
			local corners = {
				part.CFrame * CFrame.new(-sizeHalf.X, -sizeHalf.Y, -sizeHalf.Z),
				part.CFrame * CFrame.new(-sizeHalf.X, -sizeHalf.Y, sizeHalf.Z),
				part.CFrame * CFrame.new(-sizeHalf.X, sizeHalf.Y, -sizeHalf.Z),
				part.CFrame * CFrame.new(-sizeHalf.X, sizeHalf.Y, sizeHalf.Z),
				part.CFrame * CFrame.new(sizeHalf.X, -sizeHalf.Y, -sizeHalf.Z),
				part.CFrame * CFrame.new(sizeHalf.X, -sizeHalf.Y, sizeHalf.Z),
				part.CFrame * CFrame.new(sizeHalf.X, sizeHalf.Y, -sizeHalf.Z),
				part.CFrame * CFrame.new(sizeHalf.X, sizeHalf.Y, sizeHalf.Z),
			}
			for _, cornerCFrame in pairs(corners) do
				local x, y, z = cornerCFrame:GetComponents()
				local values = { x, y, z }
				bounds.Min:parse(values)
				bounds.Max:parse(values)
			end
		end
	end
	local boundMin = Vector3.new(unpack(bounds.Min.Values))
	local boundMax = Vector3.new(unpack(bounds.Max.Values)) + Vector3.new(0, self.RegionHeight, 0)
	if self.DisplayBoundParts then
		local boundParts = { BoundMin = boundMin, BoundMax = boundMax }
		for boundName, boundCFrame in pairs(boundParts) do
			local part = Instance.new("Part")
			part.Anchored = true
			part.CanCollide = false
			part.Transparency = 0.5
			part.Size = Vector3.new(4, 4, 4)
			part.Color = Color3.fromRGB(255, 0, 0)
			part.CFrame = CFrame.new(boundCFrame)
			part.Name = boundName
			part.Parent = self.Group
		end
	end
	local region = Region3.new(boundMin, boundMax)
	return region
end

function Zone:getPlayersInRegion()
	local players = Players:GetPlayers()
	local playerCharacters = {}
	for _, player in pairs(players) do
		local char = player.Character
		local hrp = char and char:FindFirstChild("HumanoidRootPart")
		if hrp then
			table.insert(playerCharacters, hrp)
		end
	end
	local partsInRegion = workspace:FindPartsInRegion3WithWhiteList(self.Region, playerCharacters, #players)
	local charsChecked = {}
	local playersInRegion = {}
	if #partsInRegion > 0 then
		for _, part in pairs(partsInRegion) do
			local char = part.Parent
			if not charsChecked[char] then
				charsChecked[char] = true
				local player = Players:GetPlayerFromCharacter(char)
				if player then
					--table.insert(playersInRegion, player)
					playersInRegion[player] = true
				end
			end
		end
	end
	return playersInRegion
end

function Zone:getPlayer(player)
	local char = player.Character
	local hrp = char and char:FindFirstChild("HumanoidRootPart")
	if hrp then
		local origin = hrp.Position + Vector3.new(0, 4, 0)
		local lookDirection = origin + Vector3.new(0, -1, 0)
		local ray = Ray.new(origin, (lookDirection - origin).unit * self.RegionHeight)
		local groupPart = workspace:FindPartOnRayWithWhitelist(ray, self.GroupParts)
		if groupPart then
			return true
		end
	end
end

function Zone:getPlayers()
	local playersInRegion = self:getPlayersInRegion()
	local playersInZone = {}
	for _, player in pairs(playersInRegion) do
		if self:getPlayer(player) then
			if not self.PreviousPlayers[player] then
				self.PlayerAdded:Fire(player)
			end
			table.insert(playersInZone, player)
		elseif self.PreviousPlayers[player] then
			self.PlayerRemoving:Fire(player)
		end
	end
	self.PreviousPlayers = playersInZone
	return playersInZone
end

function Zone:initLoop(loopDelay)
	loopDelay = tonumber(loopDelay) or 0.5
	local loopId = HttpService:GenerateGUID(false)
	self.currentLoop = loopId
	if not self.loopInitialized then
		self.loopInitialized = true
		coroutine.wrap(function()
			while self.currentLoop == loopId do
				wait(loopDelay)
				self:getPlayers()
			end
		end)()
	end
end

function Zone:endLoop()
	self.currentLoop = nil
end

return Zone
