DQR Simple Lobby Guard - Source
Key Required

DQR Simple Lobby Guard - Source

πŸŽƒHALLOWEEN πŸŽƒ Dungeon Quest RebornBaltazar_exeBaltazar_exe 3.6K views 4.3K executesUpdated Sep 10, 2026
Unrated
00
Lua Scriptβ€’362 lines (11608 chars)
-- ============================================================
--  LOBBY GUARD  Β·  DUNGEON QUEST REBORN By Baltazar_exe
-- ============================================================

local Config = {

	-- ── 1. YOUR ACCOUNTS  (the important part) ────────────
	--
	--  USERNAMES of every account you play on (main + alts). Anyone
	--  who joins and ISN'T on this list is treated as an intruder.
	--
	--  Must be the USERNAME (the @name on the profile), NOT the
	--  display name. If your profile shows "Cool Name" but the @ is
	--  "coolname_2010", put "coolname_2010".
	--
	--  If a name is missing, that account joining your party closes
	--  your main. Case doesn't matter.
	Allow = {
		"PUT_YOUR_MAIN_USERNAME_HERE",
		"PUT_YOUR_ALT_USERNAME_HERE",
		-- add or remove lines to match how many accounts you have
	},

	-- ── 2. DISCORD ALERT  (optional) ─────────────────────
	--
	--  Leave "" if you only want the game to close, no alert.
	--  To get a URL: your server > channel settings > Integrations >
	--  Webhooks > New Webhook > Copy URL.
	WebhookURL = "",

	-- Ping you in the message. Discord > Settings > Advanced >
	-- Developer Mode on > right-click your name > Copy ID. Numbers
	-- only. "" = no ping. PingRoleId pings a role instead.
	PingUserId = "",
	PingRoleId = "",

	-- ── 3. BEHAVIOUR ─────────────────────────────────────

	-- true  = close the game on an intruder
	-- false = only send the alert, keep playing (for testing)
	CloseGame = true,

	GuardInLobby   = false, -- only turn on if you're in your own private/VIP server
	GuardInDungeon = true,

	-- When detection isn't sure where you are (happens during load /
	-- mid-teleport). Off, because that moment usually means you're
	-- entering the full lobby, and closing then would be a mistake.
	GuardWhenUnknown = false,

	CloseDelay  = 1.0, -- seconds after the alert before closing (lets the webhook actually send)
	RescanEvery = 1,   -- seconds between player-list scans
}

-- ============================================================
--  Logic below. Nothing to edit.
-- ============================================================

local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")

-- Can be nil on auto-exec (script runs before the client builds the
-- player). Reassigned in waitReady() once it appears - no "local"
-- there, it's this same variable. With it nil, "not you" would be
-- true for YOU and the script would close the game every load.
local LocalPlayer = Players.LocalPlayer

if getgenv and getgenv().__LobbyGuardUnload then
	pcall(getgenv().__LobbyGuardUnload)
end

local conns = {}
local fired = false   -- already alerted
local running = true  -- script alive (kept separate so the scan doesn't stop after the first alert)
local lastWhere = nil

local function allowed(name)
	local want = string.lower(tostring(name))
	for _, n in ipairs(Config.Allow) do
		if string.lower(tostring(n)) == want then return true end
	end
	return false
end

-- ── lobby or dungeon? ────────────────────────────────────
--  dungeon = the "dungeon" folder exists AND has "room 1"/"room 2"...
--  children (the folder alone also shows as a lobby preview).
--  lobby = the "games"/"vipServer"/"bossLobbies" hub folders.
--  The folder blinks out for a moment on room transitions, so a 2s
--  sticky window keeps it reading "dungeon" through the gap.
local DUNGEON_STICKY = 2
local lastDungeonAt = 0

local function getDungeonFolder()
	local d = workspace:FindFirstChild("dungeon") or workspace:FindFirstChild("Dungeon")
	if d then return d end
	for _, ch in ipairs(workspace:GetChildren()) do
		if string.lower(ch.Name) == "dungeon" then return ch end
	end
	return nil
end

local function folderLooksLikeDungeon(d)
	if not (d and d.Parent) then return false end
	if d:FindFirstChild("room 1") or d:FindFirstChild("Room 1") or d:FindFirstChild("room1") then
		return true
	end
	for _, ch in ipairs(d:GetChildren()) do
		local n = string.lower(ch.Name)
		if n:match("^room%s*%d+") or n:match("^room%d+") then return true end
	end
	return false
end

local function lobbyHub()
	return workspace:FindFirstChild("games") ~= nil
		or workspace:FindFirstChild("vipServer") ~= nil
		or workspace:FindFirstChild("bossLobbies") ~= nil
end

-- "dungeon" | "lobby" | "?"
local function whereAmI()
	local ok, res = pcall(function()
		if folderLooksLikeDungeon(getDungeonFolder()) then
			lastDungeonAt = os.clock()
			return "dungeon"
		end
		if lastDungeonAt > 0 and os.clock() - lastDungeonAt < DUNGEON_STICKY then
			return "dungeon"
		end
		if lobbyHub() then return "lobby" end
		return "?"
	end)
	return ok and res or "?"
end

local function guardActive()
	local w = whereAmI()
	if w == "dungeon" then return Config.GuardInDungeon ~= false, w end
	if w == "lobby" then return Config.GuardInLobby ~= false, w end
	return Config.GuardWhenUnknown ~= false, w
end

local function intruders()
	local list = {}
	if not LocalPlayer then return list end
	for _, p in ipairs(Players:GetPlayers()) do
		if p ~= LocalPlayer and not allowed(p.Name) then
			list[#list + 1] = p
		end
	end
	return list
end

-- Executor's request/http_request first: HttpService:PostAsync is
-- blocked for off-game domains in most executors. HttpService is the
-- fallback.
local function sendWebhook(payload)
	local url = Config.WebhookURL
	if type(url) ~= "string" or url == "" then return false, "no URL" end
	local body = HttpService:JSONEncode(payload)

	local reqFn = (syn and syn.request) or (http and http.request) or http_request
		or request or (fluxus and fluxus.request)

	if reqFn then
		local ok, res = pcall(reqFn, {
			Url = url,
			Method = "POST",
			Headers = { ["Content-Type"] = "application/json" },
			Body = body,
		})
		if ok then return true end
		return false, tostring(res)
	end

	local ok, err = pcall(function()
		HttpService:PostAsync(url, body, Enum.HttpContentType.ApplicationJson)
	end)
	return ok, (not ok) and tostring(err) or nil
end

local function mentionText()
	if Config.PingRoleId ~= "" then return "<@&" .. Config.PingRoleId .. ">" end
	if Config.PingUserId ~= "" then return "<@" .. Config.PingUserId .. ">" end
	return ""
end

-- Webhook is sent BEFORE closing, and the call is blocking on
-- purpose: closing first would kill the process with the request
-- still queued. Closing is the last line.
local function alert(who)
	if fired then return end
	fired = true

	local names = {}
	for _, p in ipairs(who) do
		names[#names + 1] = ("%s (%s Β· ID %d)"):format(p.DisplayName, p.Name, p.UserId)
	end
	if #names == 0 then names[1] = "?" end

	local jobId = tostring(game.JobId)
	if jobId == "" then jobId = "(local / studio)" end

	local content = mentionText()
	if content ~= "" then content = content .. " player joined your server" end

	local ok, err = sendWebhook({
		content = content ~= "" and content or nil,
		embeds = { {
			title = "Player detected in the server",
			description = ("**%d** player(s) besides you:\n%s"):format(#who, table.concat(names, "\n")),
			color = 0xF0546C,
			fields = {
				{ name = "Your account", value = LocalPlayer
					and ("%s (%s)"):format(LocalPlayer.DisplayName, LocalPlayer.Name)
					or "(unknown)", inline = true },
				{ name = "Players in server", value = tostring(#Players:GetPlayers()), inline = true },
				{ name = "Where", value = whereAmI(), inline = true },
				{ name = "PlaceId", value = tostring(game.PlaceId), inline = true },
				{ name = "JobId", value = jobId, inline = false },
				{ name = "Action", value = Config.CloseGame and "closing the game" or "alert only (CloseGame = false)", inline = false },
			},
		} },
	})

	if Config.WebhookURL == "" then
		print("[LobbyGuard] intruder:", table.concat(names, ", "), "(no webhook configured)")
	elseif ok then
		print("[LobbyGuard] webhook sent:", table.concat(names, ", "))
	else
		warn("[LobbyGuard] webhook FAILED:", err, "- continuing anyway")
	end

	if not Config.CloseGame then
		task.delay(30, function() fired = false end) -- re-arm so it alerts again for the next one
		return
	end

	task.wait(Config.CloseDelay or 1)

	-- 1) most executors implement this as "kill the client"
	local shut = pcall(function() game:Shutdown() end)
	if shut then return end

	-- 2) always exists, but DISCONNECTS instead of closing the window
	pcall(function()
		if LocalPlayer then
			LocalPlayer:Kick("LobbyGuard: another player joined the server.")
		end
	end)
end

local function check()
	if fired then return end

	local on, w = guardActive() -- checked every scan: you move in and out of dungeons all run
	if not on then
		if lastWhere ~= w then
			lastWhere = w
			print("[LobbyGuard] in " .. w .. " - guard off here")
		end
		return
	end
	if lastWhere ~= w then
		lastWhere = w
		print("[LobbyGuard] in " .. w .. " - watching")
	end

	local who = intruders()
	if #who > 0 then
		alert(who)
	end
end

-- Nothing can arm before LocalPlayer exists - not even PlayerAdded:
-- with it nil, "p == LocalPlayer" never matches and YOUR own join
-- would fire the alert.
local function waitReady()
	local t0 = os.clock()
	while not Players.LocalPlayer and os.clock() - t0 < 60 do
		task.wait(0.1)
	end
	LocalPlayer = Players.LocalPlayer
	if not LocalPlayer then
		warn("[LobbyGuard] Players.LocalPlayer never appeared - guard NOT armed")
		return false
	end

	t0 = os.clock()
	while not game:IsLoaded() and os.clock() - t0 < 60 do
		task.wait(0.1)
	end
	return true
end

print(("[LobbyGuard] loaded Β· close=%s Β· accounts=%d Β· scan every %ss"):format(
	tostring(Config.CloseGame), #Config.Allow, tostring(Config.RescanEvery)))
print(("[LobbyGuard] guard: lobby=%s Β· dungeon=%s Β· unknown=%s"):format(
	tostring(Config.GuardInLobby ~= false),
	tostring(Config.GuardInDungeon ~= false),
	tostring(Config.GuardWhenUnknown ~= false)))
if Config.WebhookURL == "" then
	warn("[LobbyGuard] WebhookURL empty - it will close the game with NO Discord alert")
end
if Config.CloseGame and #Config.Allow == 0 then
	warn("[LobbyGuard] account list EMPTY and CloseGame on - any join (incl. your alt) closes this account")
end
do
	local placeholders = 0
	for _, n in ipairs(Config.Allow) do
		if string.find(string.upper(tostring(n)), "PUT_YOUR") == 1 then placeholders = placeholders + 1 end
	end
	if placeholders > 0 then
		warn(("[LobbyGuard] you left %d example name(s) (\"PUT_YOUR_...\") in Config.Allow - replace with your real USERNAMES"):format(placeholders))
	end
end

task.spawn(function()
	if not Players.LocalPlayer then
		print("[LobbyGuard] waiting for the player to arm...")
	end
	if not waitReady() then return end

	conns[#conns + 1] = Players.PlayerAdded:Connect(function(p)
		if p == LocalPlayer then return end
		if allowed(p.Name) then
			print("[LobbyGuard] " .. p.Name .. " is on the list - ignored")
			return
		end
		local on, w = guardActive()
		if not on then
			print(("[LobbyGuard] %s joined, but I'm in %s - guard off here"):format(p.Name, w))
			return
		end
		alert({ p })
	end)

	print(("[LobbyGuard] armed Β· %d player(s) in server Β· you = %s"):format(
		#Players:GetPlayers(), tostring(LocalPlayer.Name)))

	-- Periodic scan, forever. PlayerAdded only catches joins from now
	-- on; this catches whoever was already there or still replicating
	-- when the script loaded. Only the unload stops it.
	while running do
		check()
		task.wait(math.max(tonumber(Config.RescanEvery) or 3, 1))
	end
end)

if getgenv then
	getgenv().__LobbyGuardUnload = function()
		for _, c in ipairs(conns) do
			pcall(function() c:Disconnect() end)
		end
		conns = {}
		fired = true
		running = false
		getgenv().__LobbyGuardUnload = nil
		print("[LobbyGuard] unloaded")
	end
end

Features & Information

Kicks you out (or closes the game) the moment anyone who isn't on your account list joins your server - so nobody catches you AFK-farming. - Whitelist your own accounts (main + alts) so they don't trigger it - Works in dungeons and/or the lobby - your choice - Optional Discord alert telling you who joined and where - Test mode: alert only, without closing

Community Comments (0)

No comments yet for this script.

More Scripts for πŸŽƒHALLOWEEN πŸŽƒ Dungeon Quest Reborn