Allow custom scripts to run in a plugin?

I currently have a plugin that allows a user to simulate the physics of a list of parts in edit mode.

Currently it doesn’t run scripts inside of the simulated assemblies. I want to add the option to select which scripts to run during simulation and actually run them without the user needing to modify the scripts beforehand, but i dont know how to. i want to run it in such a way that i can run, stop and restart when the user start/stops the simulation. how can i achieve this?

1 Like

Since plugins have access to script.source, I’d imagine you could automatically replace any "script.Parent"s with game.Workspace.blahblah.doodad, and then use the (incredibly insecure for normal use!!!) loadstring() function. Dunno how exactly it would have to be put together, but I hope this helps nonetheless.

isnt loadstring() retricted? plugins using it cant be dsitributed.

1 Like

Maybe. I haven’t looked too much into it until just now be honest.

I just looked at another thread (Script Runner: Run scripts without running the game) and OP said the plugin got removed.

Look at this, too:

Nevermind what I said before lol because you definitely can’t upload plugins that use loadtring() or otherwise execute scripts for any reason, BUT it seems like you can use it personally as a local plugin. if you’re unsure probably CTRL+F the Terms Of Service for plugin stuff.

im looking into this atm, the only issue is that if a script is run, and has connections inside of it, even if you were to delete the script, those connections still persist. im looking for a way to completely stop a script from running when needed.

I’m not sure, then. I’m sorry I can’t help much more. I hope you find something that works!!

1 Like

thats fine thanks for the little help. currently i decided to take the LONG route and created a function that reads through the source of a script, stores every connection into a non local variable, insert them into a table, and returns it at the end:

Original Test Code

-- Test script for connection detection
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

RunService.Heartbeat:Connect(function()
	print("Heartbeat")
end)

local mouseConnection = UserInputService.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		print("Mouse clicked")
	end
end)

local keyboardConnection = UserInputService.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.Space then
		print("Space released")
	elseif input.KeyCode == Enum.KeyCode.W then
		print("W released")
	end
end)

local function playerAdded(player)
	print(player.Name .. " joined the game")

	player.CharacterAdded:Connect(function(character)
		print(player.Name .. "'s character spawned")
	end)
end

Players.PlayerAdded:Connect(playerAdded)

if true then
	local conditionalConnection = RunService.RenderStepped:Connect(function()
		print("RenderStepped")
	end)
end

Modified Code

local SCRIPTCONNECTIONS = {}

-- Test script for connection detection
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

connection_7 = RunService.Heartbeat:Connect(function()
	print("Heartbeat")
end)

mouseConnection = UserInputService.InputBegan:Connect(function(input)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		print("Mouse clicked")
	end
end)

keyboardConnection = UserInputService.InputEnded:Connect(function(input)
	if input.KeyCode == Enum.KeyCode.Space then
		print("Space released")
	elseif input.KeyCode == Enum.KeyCode.W then
		print("W released")
	end
end)

local function playerAdded(player)
	print(player.Name .. " joined the game")

	connection_28 = player.CharacterAdded:Connect(function(character)
		print(player.Name .. "'s character spawned")
	end)
end

connection_33 = Players.PlayerAdded:Connect(playerAdded)

if true then
	conditionalConnection = RunService.RenderStepped:Connect(function()
		print("RenderStepped")
	end)
end


-- Collect all connections
table.insert(SCRIPTCONNECTIONS, connection_7)
table.insert(SCRIPTCONNECTIONS, mouseConnection)
table.insert(SCRIPTCONNECTIONS, keyboardConnection)
table.insert(SCRIPTCONNECTIONS, connection_28)
table.insert(SCRIPTCONNECTIONS, connection_33)
table.insert(SCRIPTCONNECTIONS, conditionalConnection)

return SCRIPTCONNECTIONS

with this i should theoretically be able to disconnect these remotely when needed. Hopefully…

the method works! i created a test plugin to show it working. It converts any script into a module that returns its connections, so that you can disconnect them afterwards.

code:

local env = script.Parent
local gui = env.Frame

local startButton = gui.Start
local stopButton = gui.Stop

local toolbar = plugin:CreateToolbar("Script Runner")
local openButton = toolbar:CreateButton("toggleOpen", "", "", "Open")

local currentScript = nil
local connections = {}

-- Simple connection processor
local function processScriptConnections(scriptSource)
	-- Create a new script with the SCRIPTCONNECTIONS table at the top
	local newSource = "local SCRIPTCONNECTIONS = {}\n\n"

	-- Split the script into lines
	local lines = {}
	local connectionVars = {}

	for line in scriptSource:gmatch("([^\n]*)\n?") do
		table.insert(lines, line)
	end

	-- Process each line
	for i, line in ipairs(lines) do
		-- Look for connections
		if line:find(":[Cc]onnect%(") then
			-- Is this already a variable assignment?
			if line:match("^%s*local%s+([%w_]+)%s*=") then
				-- It's a local variable, change to non-local
				local varName = line:match("^%s*local%s+([%w_]+)%s*=")
				newSource = newSource .. line:gsub("^%s*local%s+", "") .. "\n"
				table.insert(connectionVars, varName)
			elseif line:match("^%s*([%w_]+)%s*=") then
				-- Already a non-local variable, keep as is
				local varName = line:match("^%s*([%w_]+)%s*=")
				newSource = newSource .. line .. "\n"
				table.insert(connectionVars, varName)
			else
				-- Inline connection, create a non-local variable
				local varName = "connection_" .. i
				local indent = line:match("^(%s*)")
				newSource = newSource .. indent .. varName .. " = " .. line:gsub("^%s*", "") .. "\n"
				table.insert(connectionVars, varName)
			end
		else
			-- Not a connection, keep the line as is
			newSource = newSource .. line .. "\n"
		end
	end

	-- Add code to collect connections at the end
	newSource = newSource .. "\n-- Collect all connections\n"

	for _, varName in ipairs(connectionVars) do
		newSource = newSource .. "table.insert(SCRIPTCONNECTIONS, " .. varName .. ")\n"
	end

	-- Add return statement
	newSource = newSource .. "\nreturn SCRIPTCONNECTIONS"

	-- Create the module script
	local moduleScript = Instance.new("ModuleScript")
	moduleScript.Name = "ConnectionsModule"
	moduleScript.Source = newSource

	return moduleScript
end


local dockWidgetInfo = DockWidgetPluginGuiInfo.new(
	Enum.InitialDockState.Float,
	false,
	false, 
	100, 
	100, 
	100, 
	100
)

local dockWidget = plugin:CreateDockWidgetPluginGui("Script Loader", dockWidgetInfo)
gui.Parent = dockWidget

openButton.Click:Connect(function()
	dockWidget.Enabled = not dockWidget.Enabled
end)

startButton.Activated:Connect(function()
	local originalScript = game.Selection:Get()[1]
	if not originalScript:IsA("Script") and not originalScript:IsA("LocalScript") then return end
	
	local processedScript = processScriptConnections(originalScript.Source)
	processedScript.Name = originalScript.Name .. "_PLUGIN"
	processedScript.Parent = originalScript.Parent
	
	currentScript = processedScript
	connections = require(processedScript)
end)

stopButton.Activated:Connect(function()
	for _, connection in connections do
		connection:Disconnect()
		connection = nil
	end
	currentScript:Destroy()
end)

the proccessScriptConnections is what detects the connections in the source and stores them into a table.

Example:

Caveats:
while it can run scripts with a while true loop, it is unable to stop it, so avoid doing that.