salahzar icon

brain.roblox

salahzar | PRO | 05/28/26 08:25:59 PM UTC | 2 ⭐ | 27258 👁️ | Never ⏰ | []
text |

14.59 KB

|

None

|

2 👍

/

0 👎

-- BRAIN.ROBLOX.LUA - AI-Driven NPC Brain for Roblox
-- Analogous to brain.lsl (Second Life). Place as a Script inside an NPC Model.
-- Model name format: "NPCName.AreaName"
-- Set a StringAttribute "ServerURL" on the Model to your Nexus server URL.
--
-- Required model structure:
--   Model (named "NPCName.AreaName", with StringAttribute ServerURL)
--     HumanoidRootPart (or any BasePart as primary)
--     Humanoid
--     ClickDetector (auto-created if missing)
--     Animation objects (optional, named to match anim= commands)
 local Players     = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local Chat        = game:GetService("Chat")
 -- ============================================
-- CONFIGURATION
-- ============================================
 local DEBUG = false
 local model    = script.Parent
local rootPart = model:FindFirstChild("HumanoidRootPart")
	or (model:IsA("Model") and model.PrimaryPart or nil)
	or model:FindFirstChildWhichIsA("BasePart")
local humanoid = model:FindFirstChildOfClass("Humanoid")
 local SERVER_URL  = model:GetAttribute("ServerURL") or ""
local NPC_NAME    = ""
local CURRENT_AREA = ""
local TIMEOUT     = 300 -- seconds
 -- ============================================
-- COLORS
-- ============================================
 local RED    = Color3.new(1, 0, 0)
local WHITE  = Color3.new(1, 1, 1)
local GREEN  = Color3.new(0, 1, 0)
local YELLOW = Color3.new(1, 1, 0)
local BLUE   = Color3.new(0.3, 0.7, 1)
 -- ============================================
-- STATE (forward-declared so functions can cross-reference)
-- ============================================
 local isConversing       = false
local currentPlayer: Player? = nil
local conversationTimer: thread? = nil
local listenConnection: RBXScriptConnection? = nil
local billboardGui: BillboardGui? = nil
 -- Forward declarations
local endConversation: (boolean) -> ()
local resetConversationTimeout: () -> ()
 -- ============================================
-- BILLBOARD LABEL
-- ============================================
 local function setLabel(text: string, color: Color3)
	if not billboardGui then
		local gui = Instance.new("BillboardGui")
		gui.Name = "StatusBillboard"
		gui.Size = UDim2.new(0, 220, 0, 70)
		gui.StudsOffset = Vector3.new(0, 3.5, 0)
		gui.AlwaysOnTop = true
		gui.Parent = rootPart
			or model:FindFirstChildWhichIsA("BasePart")
			or (model:IsA("BasePart") and model or nil)
			or model
 		local label = Instance.new("TextLabel")
		label.Name = "Label"
		label.Size = UDim2.fromScale(1, 1)
		label.BackgroundTransparency = 1
		label.TextColor3 = WHITE
		label.TextScaled = true
		label.TextWrapped = true
		label.Font = Enum.Font.GothamBold
		label.Parent = gui
 		billboardGui = gui
	end
 	local label = billboardGui:FindFirstChild("Label") :: TextLabel?
	if label then
		label.Text = text
		label.TextColor3 = color
	end
end
 -- ============================================
-- PART COLOR INDICATOR (like llSetColor in LSL)
-- ============================================
 local function setColor(color: Color3)
	for _, part in model:GetDescendants() do
		if part:IsA("BasePart") and part.Name ~= "HumanoidRootPart" then
			part.Color = color
		end
	end
end
 -- ============================================
-- NPC SPEECH
-- ============================================
 local function npcSay(message: string)
	local part = rootPart
		or model:FindFirstChildWhichIsA("BasePart")
		or (model:IsA("BasePart") and model or nil)
	if part then
		Chat:Chat(part, message, Enum.ChatColor.Blue)
	end
	if DEBUG then print("[" .. NPC_NAME .. "] " .. message) end
end
 local function ownerSay(message: string)
	print("[NPC:" .. NPC_NAME .. "] " .. message)
end
 -- ============================================
-- JSON HELPERS
-- ============================================
 local function escapeJSON(s: string): string
	s = s:gsub("\\", "\\\\")
	s = s:gsub('"',  '\\"')
	s = s:gsub("\n", "\\n")
	s = s:gsub("\r", "\\r")
	s = s:gsub("\t", "\\t")
	return s
end
 -- Minimal flat-JSON key extractor (handles string values only)
local function extractJSON(body: string, key: string): string
	-- Match "key": "value" allowing escaped quotes inside value
	local val = body:match('"' .. key .. '":%s*"(.-[^\\])"')
		or body:match('"' .. key .. '":%s*"()"')  -- empty string
	if val then
		val = val:gsub('\\"', '"')
		val = val:gsub("\\n", "\n")
		val = val:gsub("\\\\", "\\")
	end
	-- Also handle boolean/number values (returned as-is without quotes)
	if not val then
		val = body:match('"' .. key .. '":%s*([%w%.%-]+)')
	end
	return val or ""
end
 -- ============================================
-- ANIMATIONS
-- ============================================
 local animTracks: { [string]: AnimationTrack } = {}
 local function playAnim(animName: string)
	if not humanoid then return end
	animName = animName:match("^%s*(.-)%s*$") -- trim whitespace
 	local animObj = model:FindFirstChild(animName)
	if not animObj or not animObj:IsA("Animation") then
		if DEBUG then ownerSay("Animation not found: " .. animName) end
		return
	end
 	-- Stop previous track for this name if still playing
	if animTracks[animName] then
		animTracks[animName]:Stop()
	end
 	local track = humanoid:LoadAnimation(animObj)
	track:Play()
	animTracks[animName] = track
end
 -- ============================================
-- SL COMMAND PROCESSING
-- ============================================
 local function processSLCommands(slCommands: string)
	-- Parse [key=value;key=value] style blocks from sl_commands field
	for key, value in slCommands:gmatch("([%w]+)=([^;%]]+)") do
		local k = key:lower()
		if k == "anim" or k == "emote" then
			playAnim(value)
		elseif k == "llsettext" then
			setLabel(value, WHITE)
		elseif k == "face" then
			if DEBUG then ownerSay("face command (no-op in Roblox): " .. value) end
		elseif k == "teleport" then
			if DEBUG then ownerSay("teleport command: " .. value) end
		elseif k == "lookup" then
			if DEBUG then ownerSay("lookup command: " .. value) end
		end
	end
end
 -- ============================================
-- RESPONSE PARSING
-- ============================================
 local function parseAnimations(text: string)
	local animName = text:match("%[anim=([^%]]+)%]")
	if animName then
		playAnim(animName)
	end
end
 local function cleanText(text: string): string
	text = text:gsub("%[%a+=[^%]]*%]", "") -- strip [tag=value] blocks
	text = text:match("^%s*(.-)%s*$")       -- trim
	return text
end
 local function handleChatResponse(body: string)
	setColor(WHITE)
	resetConversationTimeout()
 	if DEBUG then
		ownerSay("=== RESPONSE RECEIVED === len=" .. #body)
	end
 	local npcResponse = extractJSON(body, "npc_response")
	if npcResponse == "" then
		if DEBUG then ownerSay("ERROR: npc_response empty") end
		return
	end
 	parseAnimations(npcResponse)
 	local slCommands = extractJSON(body, "sl_commands")
	if slCommands ~= "" then
		processSLCommands(slCommands)
	end
 	npcResponse = cleanText(npcResponse)
	if npcResponse ~= "" then
		npcSay(npcResponse)
	else
		if DEBUG then ownerSay("ERROR: cleaned response empty") end
	end
end
 -- ============================================
-- HTTP HELPERS
-- ============================================
 local function httpPost(endpoint: string, data: string): (boolean, string, number)
	local ok, result = pcall(HttpService.RequestAsync, HttpService, {
		Url     = SERVER_URL .. endpoint,
		Method  = "POST",
		Headers = { ["Content-Type"] = "application/json" },
		Body    = data,
	})
	if ok then
		return true, (result :: any).Body, (result :: any).StatusCode
	end
	return false, tostring(result), 0
end
 local function httpGet(endpoint: string): (boolean, string, number)
	local ok, result = pcall(HttpService.RequestAsync, HttpService, {
		Url    = SERVER_URL .. endpoint,
		Method = "GET",
	})
	if ok then
		return true, (result :: any).Body, (result :: any).StatusCode
	end
	return false, tostring(result), 0
end
 -- ============================================
-- CONVERSATION TIMEOUT
-- ============================================
 resetConversationTimeout = function()
	if conversationTimer then
		task.cancel(conversationTimer)
		conversationTimer = nil
	end
	if not isConversing then return end
	conversationTimer = task.delay(TIMEOUT, function()
		if isConversing then
			ownerSay("Timeout: ending conversation with " .. (currentPlayer and currentPlayer.Name or "?"))
			endConversation(true)
		end
	end)
end
 -- ============================================
-- CONVERSATION MANAGEMENT
-- ============================================
 endConversation = function(sayGoodbye: boolean)
	if not isConversing then return end
 	local playerName = currentPlayer and currentPlayer.Name or ""
 	-- Notify server asynchronously
	local leaveData = string.format(
		'{"player_name":"%s","npc_name":"%s","area":"%s","action":"leaving","message":"Avatar leaving","status":"end"}',
		escapeJSON(playerName), escapeJSON(NPC_NAME), escapeJSON(CURRENT_AREA)
	)
	task.spawn(function()
		httpPost("/api/leave_npc", leaveData)
	end)
 	if conversationTimer then
		task.cancel(conversationTimer)
		conversationTimer = nil
	end
	if listenConnection then
		listenConnection:Disconnect()
		listenConnection = nil
	end
 	if sayGoodbye and playerName ~= "" then
		npcSay("È stato un piacere parlare con te, " .. playerName .. "!")
	end
 	setColor(WHITE)
	currentPlayer = nil
	isConversing  = false
	setLabel("Touch to talk to\n" .. NPC_NAME, GREEN)
end
 local function sendMessage(msg: string, playerName: string)
	if not isConversing then
		if DEBUG then ownerSay("ERROR: Not conversing") end
		return
	end
 	setColor(RED)
	resetConversationTimeout()
 	local chatData = string.format(
		'{"message":"%s","player_name":"%s","npc_name":"%s","area":"%s"}',
		escapeJSON(msg), escapeJSON(playerName), escapeJSON(NPC_NAME), escapeJSON(CURRENT_AREA)
	)
 	if DEBUG then ownerSay("Sending /api/chat...") end
 	task.spawn(function()
		local ok, body, status = httpPost("/api/chat", chatData)
		if ok and status == 200 then
			handleChatResponse(body)
		else
			setColor(WHITE)
			if DEBUG then
				ownerSay("HTTP error " .. tostring(status))
				ownerSay("Body: " .. body:sub(1, 200))
			end
			npcSay("Scusa, non posso risponderti ora.")
		end
	end)
end
 local function startConversation(player: Player)
	if isConversing then return end
 	currentPlayer = player
	isConversing  = true
	setLabel("Conversing with\n" .. player.Name, BLUE)
 	local senseData = string.format(
		'{"name":"%s","npcname":"%s","area":"%s"}',
		escapeJSON(player.Name), escapeJSON(NPC_NAME), escapeJSON(CURRENT_AREA)
	)
 	if DEBUG then ownerSay("Sending /sense for " .. player.Name) end
 	task.spawn(function()
		local ok, body, status = httpPost("/sense", senseData)
		if ok and status == 200 then
			handleChatResponse(body)
			resetConversationTimeout()
 			-- Listen for player chat messages (server-side via Player.Chatted)
			listenConnection = player.Chatted:Connect(function(message: string)
				sendMessage(message, player.Name)
			end)
		else
			isConversing  = false
			currentPlayer = nil
			setLabel("Touch to talk to\n" .. NPC_NAME, GREEN)
			if DEBUG then ownerSay("Sense failed: " .. tostring(status)) end
		end
	end)
end
 -- ============================================
-- INITIALIZATION
-- ============================================
 local function init()
	if SERVER_URL == "" or not SERVER_URL:match("^https?://") then
		setLabel("ERROR: ServerURL\nattribute not set", RED)
		ownerSay("ERROR: Set StringAttribute 'ServerURL' on Model (e.g., http://212.227.64.143:5000)")
		return
	end
 	-- Parse "NPCName.AreaName" from model name
	local dotPos = model.Name:find("%.")
	if dotPos and dotPos > 1 then
		NPC_NAME     = model.Name:sub(1, dotPos - 1)
		CURRENT_AREA = model.Name:sub(dotPos + 1)
		ownerSay("NPC: " .. NPC_NAME .. " | Area: " .. CURRENT_AREA)
		ownerSay("Server: " .. SERVER_URL)
	else
		ownerSay("ERROR: Model name must be 'NPCName.AreaName'")
		setLabel("ERROR: Invalid\nmodel name", RED)
		return
	end
 	setLabel("Verifying\n" .. NPC_NAME .. "...", YELLOW)
 	-- Check server health, then verify NPC exists
	task.spawn(function()
		local ok, body, status = httpGet("/health")
		if not ok or status ~= 200 then
			setLabel("Server offline\n" .. NPC_NAME, RED)
			ownerSay("ERROR: Server not reachable (status " .. tostring(status) .. ")")
			return
		end
 		local version = extractJSON(body, "version")
		ownerSay("Server online" .. (version ~= "" and (" v" .. version) or ""))
 		local verifyData = string.format(
			'{"npc_name":"%s","area":"%s"}',
			escapeJSON(NPC_NAME), escapeJSON(CURRENT_AREA)
		)
		local vok, vbody, vstatus = httpPost("/api/npc/verify", verifyData)
		if vok and vstatus == 200 then
			local found = extractJSON(vbody, "found")
			if found == "true" then
				local caps = ""
				if extractJSON(vbody, "has_teleport") == "true" then caps = caps .. "TP|" end
				if extractJSON(vbody, "has_llsettext") == "true" then caps = caps .. "TXT|" end
				if extractJSON(vbody, "has_notecard") == "true" then caps = caps .. "NC"  end
				setLabel("Touch to talk to\n" .. NPC_NAME .. "\n[" .. caps .. "]", GREEN)
				ownerSay("NPC verified! Click to talk.")
			else
				setLabel("NPC not found\n" .. NPC_NAME, RED)
				ownerSay("ERROR: NPC '" .. NPC_NAME .. "' not found in database!")
			end
		else
			setLabel("Verify failed\n" .. NPC_NAME, RED)
			ownerSay("ERROR: Verify request failed (status " .. tostring(vstatus) .. ")")
		end
	end)
end
 -- ============================================
-- CLICK DETECTION  (replaces touch_start)
-- ============================================
 local clickDetector = model:FindFirstChildOfClass("ClickDetector")
if not clickDetector then
	clickDetector = Instance.new("ClickDetector")
	clickDetector.MaxActivationDistance = 10
	clickDetector.Parent = rootPart or model
end
 clickDetector.MouseClick:Connect(function(player: Player)
	-- Second click by same player ends conversation
	if isConversing and currentPlayer == player then
		endConversation(true)
		return
	end
	-- Ignore click if NPC is busy with someone else
	if isConversing then return end
 	startConversation(player)
end)
 -- End conversation when player leaves the game
Players.PlayerRemoving:Connect(function(player: Player)
	if isConversing and currentPlayer == player then
		endConversation(false)
	end
end)
 -- ============================================
-- RUN
-- ============================================
 init()

Comments

  • Zorsimir icon
    05/28/26 08:27:59 PM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  • Rongakon icon
    05/29/26 01:33:39 AM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
     
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
    
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  • Geltatis icon
    06/10/26 08:03:23 PM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
     
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
    
  • Ulrredar icon
    06/12/26 07:02:23 AM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
     
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
    
  • Tazmovas icon
    06/13/26 12:33:16 PM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 38% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from Swapzone — instant swap).
    
  • Monrozil icon
    06/14/26 05:35:07 PM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
     
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
    
  • Xenkolix icon
    06/15/26 09:08:26 PM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
     
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).
    
  • Borwedorn icon
    06/16/26 08:02:19 AM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    ✅ Leaked Exploit Documentation:
     
    https://docs.google.com/document/d/1ifNm-s74mX7GChaEzSJ1dVQCy1SrSxlMVRYi8ys0rgQ/edit?usp=sharing
     
    This made me $13,000 in 2 days.
     
    Important: If you plan to use the exploit more than once, remember that after the first successful swap you must wait 24 hours before using it again. Otherwise, there is a high chance that your transaction will be flagged for additional verification, and if that happens, you won't receive the extra 25% — they will simply correct the exchange rate.
    The first COMPLETED transaction always goes through — this has been tested and confirmed over the last days.
     
    Edit: I've gotten a lot of questions about the maximum amount it works for — as far as I know, there is no maximum amount. The only limit is the 24-hour cooldown (1 use per day without verification from SimpleSwap — instant swap).