@adiholick/nvstudio-mcp 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +47 -11
- package/dist/roblox-bridge.js +64 -5
- package/package.json +3 -2
- package/studio-plugin/Main.server.lua +291 -0
- package/studio-plugin/create_instance.lua +24 -0
- package/studio-plugin/delete_instance.lua +31 -0
- package/studio-plugin/execute_luau.lua +71 -0
- package/studio-plugin/generate_terrain.lua +23 -0
- package/studio-plugin/get_children.lua +7 -0
- package/studio-plugin/get_logs.lua +7 -0
- package/studio-plugin/get_properties.lua +52 -0
- package/studio-plugin/get_script_source.lua +7 -0
- package/studio-plugin/insert_asset.lua +18 -0
- package/studio-plugin/nvstudio_mcp.rbxmx +1337 -0
- package/studio-plugin/rollback_script.lua +13 -0
- package/studio-plugin/script_grep.lua +63 -0
- package/studio-plugin/search_instance.lua +20 -0
- package/studio-plugin/ui_manager.lua +443 -0
- package/studio-plugin/update_script_source.lua +16 -0
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
|
2
|
+
<External>null</External>
|
|
3
|
+
<External>nil</External>
|
|
4
|
+
<Item class="Folder" referent="RBX0497167F99814CA6A2096BB729707905">
|
|
5
|
+
<Properties>
|
|
6
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
7
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
8
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
9
|
+
<string name="Name">nvstudio_mcp</string>
|
|
10
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
11
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
12
|
+
</Properties>
|
|
13
|
+
<Item class="Script" referent="RBX749E4D5306E74B7C8E355593066F1D2B">
|
|
14
|
+
<Properties>
|
|
15
|
+
<ProtectedString name="Source"><![CDATA[local HttpService = game:GetService("HttpService")
|
|
16
|
+
local LogService = game:GetService("LogService")
|
|
17
|
+
|
|
18
|
+
local TASK_URL = "http://localhost:3055/api/tasks"
|
|
19
|
+
local RESPONSE_URL = "http://localhost:3055/api/response"
|
|
20
|
+
|
|
21
|
+
-- Generate Unique Studio ID (menghindari bentrok jika 2 Studio terbuka)
|
|
22
|
+
local STUDIO_ID = HttpService:GenerateGUID(false)
|
|
23
|
+
|
|
24
|
+
-- Import UI Manager (Procedural Dashboard)
|
|
25
|
+
local uiModule = script.Parent:FindFirstChild("ui_manager")
|
|
26
|
+
local ui = nil
|
|
27
|
+
|
|
28
|
+
-- Membangun Tombol Toolbar (Toggle)
|
|
29
|
+
local toolbar = plugin:CreateToolbar("NVStudio MCP")
|
|
30
|
+
local toggleBtn = toolbar:CreateButton("Dashboard", "Buka Dashboard NVStudio MCP", "rbxassetid://82696893597722")
|
|
31
|
+
|
|
32
|
+
-- Global State & Context AI
|
|
33
|
+
local ctx = {
|
|
34
|
+
scriptHistory = {},
|
|
35
|
+
recentLogs = {},
|
|
36
|
+
HttpService = HttpService,
|
|
37
|
+
studioId = STUDIO_ID
|
|
38
|
+
}
|
|
39
|
+
local MAX_LOGS = 50
|
|
40
|
+
local isConnected = false
|
|
41
|
+
local autoConnect = plugin:GetSetting("AutoConnect") or false
|
|
42
|
+
local isPolling = false
|
|
43
|
+
local isClickDebounce = false -- Mencegah rapid double-click
|
|
44
|
+
|
|
45
|
+
-- Deklarasi fungsi polling di awal agar bisa dipanggil oleh UI
|
|
46
|
+
local startPolling
|
|
47
|
+
local stopPolling
|
|
48
|
+
|
|
49
|
+
-- Inisialisasi UI jika modul ditemukan
|
|
50
|
+
if uiModule then
|
|
51
|
+
local UIManager = require(uiModule)
|
|
52
|
+
ui = UIManager.init(plugin, STUDIO_ID)
|
|
53
|
+
|
|
54
|
+
-- Sync Initial State ke UI
|
|
55
|
+
ui:setAutoConnectState(autoConnect)
|
|
56
|
+
ui:setStatus("disconnected")
|
|
57
|
+
|
|
58
|
+
-- Event Toggle GUI Panel
|
|
59
|
+
toggleBtn.Click:Connect(function()
|
|
60
|
+
ui:toggleVisibility()
|
|
61
|
+
end)
|
|
62
|
+
|
|
63
|
+
-- Event Tombol Connect (dengan debounce untuk mencegah double-click)
|
|
64
|
+
ui.connectBtn.MouseButton1Click:Connect(function()
|
|
65
|
+
if isClickDebounce then return end
|
|
66
|
+
isClickDebounce = true
|
|
67
|
+
|
|
68
|
+
if not isConnected then
|
|
69
|
+
-- CONNECT (manual) — reset manual disconnect flag agar auto-detect bisa bekerja
|
|
70
|
+
isManualDisconnect = false
|
|
71
|
+
isConnected = true
|
|
72
|
+
ui:setStatus("waiting")
|
|
73
|
+
ui:addLog("[System] Koneksi manual dimulai. Mencari AI Server (Waiting)...", Color3.fromRGB(255, 220, 100))
|
|
74
|
+
startPolling()
|
|
75
|
+
else
|
|
76
|
+
-- DISCONNECT — hentikan polling dan tandai manual disconnect
|
|
77
|
+
stopPolling()
|
|
78
|
+
ui:setStatus("disconnected")
|
|
79
|
+
ui:addLog("[System] Koneksi dihentikan oleh pengguna.", Color3.fromRGB(255, 100, 100))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
task.delay(0.3, function()
|
|
83
|
+
isClickDebounce = false
|
|
84
|
+
end)
|
|
85
|
+
end)
|
|
86
|
+
|
|
87
|
+
-- Event Custom Checkbox (Auto-Connect)
|
|
88
|
+
ui.autoConnectBtn.MouseButton1Click:Connect(function()
|
|
89
|
+
autoConnect = not autoConnect
|
|
90
|
+
plugin:SetSetting("AutoConnect", autoConnect)
|
|
91
|
+
ui:setAutoConnectState(autoConnect)
|
|
92
|
+
ui:addLog("[System] Preferensi Auto-Connect disimpan: " .. tostring(autoConnect), Color3.fromRGB(200, 200, 255))
|
|
93
|
+
end)
|
|
94
|
+
else
|
|
95
|
+
-- Fallback aman jika pengguna lupa meng-copy ui_manager.lua
|
|
96
|
+
toggleBtn.Click:Connect(function()
|
|
97
|
+
print("[NVStudio MCP] ERROR: Modul 'ui_manager' tidak ditemukan di dalam Folder. GUI Gagal dimuat!")
|
|
98
|
+
end)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
-- Handler Output Studio (Untuk AI dan GUI)
|
|
102
|
+
local function addSystemLog(msg, msgType)
|
|
103
|
+
local color = Color3.fromRGB(200, 200, 200)
|
|
104
|
+
local prefix = "[INFO] "
|
|
105
|
+
if msgType == Enum.MessageType.MessageError then
|
|
106
|
+
prefix = "[ERROR] "
|
|
107
|
+
color = Color3.fromRGB(255, 100, 100)
|
|
108
|
+
elseif msgType == Enum.MessageType.MessageWarning then
|
|
109
|
+
prefix = "[WARN] "
|
|
110
|
+
color = Color3.fromRGB(255, 200, 100)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
local fullMsg = prefix .. msg
|
|
114
|
+
|
|
115
|
+
-- 1. Simpan di memori untuk disedot AI
|
|
116
|
+
table.insert(ctx.recentLogs, fullMsg)
|
|
117
|
+
if #ctx.recentLogs > MAX_LOGS then
|
|
118
|
+
table.remove(ctx.recentLogs, 1)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
-- 2. Cetak ke Terminal GUI Plugin
|
|
122
|
+
if ui then
|
|
123
|
+
ui:addLog(fullMsg, color)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
-- Ikat listener output
|
|
128
|
+
LogService.MessageOut:Connect(addSystemLog)
|
|
129
|
+
|
|
130
|
+
-- Memuat semua module skills secara dinamis
|
|
131
|
+
local skills = {}
|
|
132
|
+
for _, module in ipairs(script.Parent:GetChildren()) do
|
|
133
|
+
if module:IsA("ModuleScript") and module.Name ~= "ui_manager" then
|
|
134
|
+
skills[module.Name] = require(module)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
-- Fungsi Helper Pathing
|
|
139
|
+
local function getInstanceFromPath(pathString)
|
|
140
|
+
local parts = string.split(pathString, ".")
|
|
141
|
+
local current = game
|
|
142
|
+
|
|
143
|
+
if parts[1] == "game" then table.remove(parts, 1) end
|
|
144
|
+
|
|
145
|
+
for _, part in ipairs(parts) do
|
|
146
|
+
local found = current:FindFirstChild(part)
|
|
147
|
+
if not found then return nil end
|
|
148
|
+
current = found
|
|
149
|
+
end
|
|
150
|
+
return current
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
-- Memproses Task Eksekusi dari AI
|
|
154
|
+
local function processTask(taskData)
|
|
155
|
+
if not taskData or not taskData.id or not taskData.command then return nil end
|
|
156
|
+
|
|
157
|
+
if ui then
|
|
158
|
+
ui:addLog("[AI Command] Menerima instruksi: " .. taskData.command, Color3.fromRGB(150, 200, 255))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
local targetInstance = getInstanceFromPath(taskData.target)
|
|
162
|
+
if not targetInstance then
|
|
163
|
+
return {
|
|
164
|
+
id = taskData.id,
|
|
165
|
+
status = "error",
|
|
166
|
+
error = "Target path tidak ditemukan: " .. taskData.target
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
local skillFunc = skills[taskData.command]
|
|
171
|
+
if not skillFunc then
|
|
172
|
+
return {
|
|
173
|
+
id = taskData.id,
|
|
174
|
+
status = "error",
|
|
175
|
+
error = "Command tidak dikenali oleh Studio: " .. taskData.command
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
-- Eksekusi skill AI
|
|
180
|
+
local result = skillFunc(targetInstance, taskData.data, ctx, taskData.target)
|
|
181
|
+
result.id = taskData.id
|
|
182
|
+
|
|
183
|
+
-- Cetak hasil eksekusi ke Terminal GUI
|
|
184
|
+
if ui then
|
|
185
|
+
local color = result.status == "success" and Color3.fromRGB(100, 255, 100) or Color3.fromRGB(255, 100, 100)
|
|
186
|
+
ui:addLog("[AI Result] " .. tostring(result.result or result.error), color)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
return result
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
-- Menghentikan polling dengan bersih (dipanggil oleh tombol Disconnect)
|
|
193
|
+
local isManualDisconnect = false -- Mencegah auto-reconnect saat user sengaja disconnect
|
|
194
|
+
|
|
195
|
+
function stopPolling()
|
|
196
|
+
isConnected = false
|
|
197
|
+
isManualDisconnect = true
|
|
198
|
+
-- isPolling akan menjadi false secara otomatis saat loop keluar
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
-- Background Polling Loop (Dapat dihentikan oleh user)
|
|
202
|
+
function startPolling()
|
|
203
|
+
if isPolling then return end
|
|
204
|
+
isPolling = true
|
|
205
|
+
|
|
206
|
+
task.spawn(function()
|
|
207
|
+
while isConnected do
|
|
208
|
+
local success, response = pcall(function()
|
|
209
|
+
-- Tambahkan cache-buster agar Studio tidak me-cache response HTTP
|
|
210
|
+
local cacheBuster = "?t=" .. tostring(os.clock())
|
|
211
|
+
return HttpService:GetAsync(TASK_URL .. cacheBuster)
|
|
212
|
+
end)
|
|
213
|
+
|
|
214
|
+
-- PENTING: Cek ulang isConnected SETELAH HTTP request selesai.
|
|
215
|
+
-- Jika user menekan Disconnect saat request sedang berjalan,
|
|
216
|
+
-- kita TIDAK boleh menimpa status UI "disconnected" yang sudah di-set.
|
|
217
|
+
if not isConnected then
|
|
218
|
+
break
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if success and response then
|
|
222
|
+
-- JIKA PING SUKSES, Node.js menyala (IDE / Agen Sedang Terbuka!)
|
|
223
|
+
if ui then ui:setStatus("connected") end
|
|
224
|
+
|
|
225
|
+
local decodeSuccess, taskData = pcall(function()
|
|
226
|
+
return HttpService:JSONDecode(response)
|
|
227
|
+
end)
|
|
228
|
+
|
|
229
|
+
if decodeSuccess and taskData and taskData.id then
|
|
230
|
+
local resultData = processTask(taskData)
|
|
231
|
+
if resultData then
|
|
232
|
+
pcall(function()
|
|
233
|
+
local jsonResult = HttpService:JSONEncode(resultData)
|
|
234
|
+
HttpService:PostAsync(RESPONSE_URL, jsonResult, Enum.HttpContentType.ApplicationJson)
|
|
235
|
+
end)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
else
|
|
239
|
+
-- Fetch gagal (Node.js server mati karena IDE/Antigravity/Cursor ditutup)
|
|
240
|
+
if ui then ui:setStatus("waiting") end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
-- Cek lagi sebelum sleep (agar disconnect lebih responsif)
|
|
244
|
+
if not isConnected then
|
|
245
|
+
break
|
|
246
|
+
end
|
|
247
|
+
task.wait(1)
|
|
248
|
+
end
|
|
249
|
+
isPolling = false
|
|
250
|
+
end)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
-- ══════════════════════════════════════════════════════════════════════
|
|
254
|
+
-- AUTO-DETECT: Background heartbeat yang SELALU berjalan.
|
|
255
|
+
-- Mendeteksi apakah server MCP aktif setiap 2 detik.
|
|
256
|
+
-- Jika terdeteksi aktif → otomatis connect (tanpa perlu tekan tombol).
|
|
257
|
+
-- Jika terdeteksi mati → otomatis update status.
|
|
258
|
+
-- ══════════════════════════════════════════════════════════════════════
|
|
259
|
+
task.spawn(function()
|
|
260
|
+
task.wait(1) -- Tunggu sebentar agar UI siap
|
|
261
|
+
|
|
262
|
+
if ui then
|
|
263
|
+
ui:addLog("[System] Plugin termuat. Auto-detect server MCP aktif...", Color3.fromRGB(200, 200, 200))
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
while true do
|
|
267
|
+
-- Skip heartbeat jika polling loop sudah aktif (ia sudah menangani semuanya)
|
|
268
|
+
if not isPolling then
|
|
269
|
+
local success, errorMsg = pcall(function()
|
|
270
|
+
local cacheBuster = "?t=" .. tostring(os.clock())
|
|
271
|
+
return HttpService:GetAsync(TASK_URL .. cacheBuster)
|
|
272
|
+
end)
|
|
273
|
+
|
|
274
|
+
if success then
|
|
275
|
+
-- Server terdeteksi aktif!
|
|
276
|
+
if not isConnected and not isManualDisconnect then
|
|
277
|
+
isConnected = true
|
|
278
|
+
if ui then
|
|
279
|
+
ui:setStatus("connected")
|
|
280
|
+
ui:addLog("[System] Server MCP terdeteksi! Auto-connected.", Color3.fromRGB(100, 255, 100))
|
|
281
|
+
end
|
|
282
|
+
startPolling()
|
|
283
|
+
end
|
|
284
|
+
else
|
|
285
|
+
-- Server tidak aktif
|
|
286
|
+
if isConnected then
|
|
287
|
+
-- Server baru saja mati (sebelumnya connected) — polling loop juga akan exit
|
|
288
|
+
isConnected = false
|
|
289
|
+
if ui then
|
|
290
|
+
ui:setStatus("waiting")
|
|
291
|
+
ui:addLog("[System] Server MCP terputus. Menunggu reconnect...", Color3.fromRGB(255, 200, 100))
|
|
292
|
+
end
|
|
293
|
+
elseif not isManualDisconnect then
|
|
294
|
+
if ui then
|
|
295
|
+
ui:setStatus("waiting")
|
|
296
|
+
-- HANYA print error jika belum terkoneksi, untuk debugging
|
|
297
|
+
ui:addLog("[Debug] Deteksi gagal: " .. tostring(errorMsg), Color3.fromRGB(150, 150, 150))
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
task.wait(2)
|
|
304
|
+
end
|
|
305
|
+
end)
|
|
306
|
+
]]></ProtectedString>
|
|
307
|
+
<bool name="Disabled">false</bool>
|
|
308
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
309
|
+
<token name="RunContext">0</token>
|
|
310
|
+
<string name="ScriptGuid">{260D8035-CDB9-4D28-83B4-92903E74BA82}</string>
|
|
311
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
312
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
313
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
314
|
+
<string name="Name">Main</string>
|
|
315
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
316
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
317
|
+
</Properties>
|
|
318
|
+
</Item>
|
|
319
|
+
<Item class="ModuleScript" referent="RBXF88A8EA0FA1B449280110AA8E425E2C9">
|
|
320
|
+
<Properties>
|
|
321
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
322
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
323
|
+
local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
|
|
324
|
+
if not success or not config.ClassName then
|
|
325
|
+
return { status = "error", error = "Format data JSON tidak valid atau ClassName hilang." }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
local newSuccess, newInst = pcall(function()
|
|
329
|
+
local inst = Instance.new(config.ClassName)
|
|
330
|
+
inst.Parent = targetInstance
|
|
331
|
+
|
|
332
|
+
if config.Properties then
|
|
333
|
+
for prop, val in pairs(config.Properties) do
|
|
334
|
+
pcall(function() inst[prop] = val end)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
return inst
|
|
338
|
+
end)
|
|
339
|
+
|
|
340
|
+
if newSuccess then
|
|
341
|
+
return { status = "success", result = "Instance '"..config.ClassName.."' berhasil dibuat di " .. targetPath }
|
|
342
|
+
else
|
|
343
|
+
return { status = "error", error = "Gagal membuat Instance. Pastikan ClassName valid." }
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
]]></ProtectedString>
|
|
347
|
+
<string name="ScriptGuid">{77371DDE-9628-42F5-BFF7-C6B802AB9AC0}</string>
|
|
348
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
349
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
350
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
351
|
+
<string name="Name">create_instance</string>
|
|
352
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
353
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
354
|
+
</Properties>
|
|
355
|
+
</Item>
|
|
356
|
+
<Item class="ModuleScript" referent="RBXA056230ABABC48FFBC66487DC4B83FB2">
|
|
357
|
+
<Properties>
|
|
358
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
359
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
360
|
+
local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
|
|
361
|
+
if not success or not config.Size or not config.Position or not config.Material then
|
|
362
|
+
return { status = "error", error = "Format JSON tidak valid. Membutuhkan Size, Position, dan Material." }
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
local fillSuccess, err = pcall(function()
|
|
366
|
+
local terrain = game.Workspace.Terrain
|
|
367
|
+
local materialEnum = Enum.Material[config.Material] or Enum.Material.Grass
|
|
368
|
+
|
|
369
|
+
local size = Vector3.new(config.Size[1], config.Size[2], config.Size[3])
|
|
370
|
+
local position = Vector3.new(config.Position[1], config.Position[2], config.Position[3])
|
|
371
|
+
|
|
372
|
+
local region = Region3.new(position - (size/2), position + (size/2))
|
|
373
|
+
terrain:FillRegion(region:ExpandToGrid(4), 4, materialEnum)
|
|
374
|
+
end)
|
|
375
|
+
|
|
376
|
+
if fillSuccess then
|
|
377
|
+
return { status = "success", result = "Terrain (" .. config.Material .. ") berhasil di-generate." }
|
|
378
|
+
else
|
|
379
|
+
return { status = "error", error = "Gagal men-generate Terrain. Pastikan tipe data array angka valid: " .. tostring(err) }
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
]]></ProtectedString>
|
|
383
|
+
<string name="ScriptGuid">{5D7B3DEA-8687-47A3-97CF-A969DAAF075B}</string>
|
|
384
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
385
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
386
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
387
|
+
<string name="Name">generate_terrain</string>
|
|
388
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
389
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
390
|
+
</Properties>
|
|
391
|
+
</Item>
|
|
392
|
+
<Item class="ModuleScript" referent="RBX020FB330A8554FAFA0499639D23AB46D">
|
|
393
|
+
<Properties>
|
|
394
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
395
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
396
|
+
local children = {}
|
|
397
|
+
for _, child in ipairs(targetInstance:GetChildren()) do
|
|
398
|
+
table.insert(children, child.Name .. " (" .. child.ClassName .. ")")
|
|
399
|
+
end
|
|
400
|
+
return { status = "success", result = children }
|
|
401
|
+
end
|
|
402
|
+
]]></ProtectedString>
|
|
403
|
+
<string name="ScriptGuid">{52133AC5-2F28-4C9D-A861-C726088A0450}</string>
|
|
404
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
405
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
406
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
407
|
+
<string name="Name">get_children</string>
|
|
408
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
409
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
410
|
+
</Properties>
|
|
411
|
+
</Item>
|
|
412
|
+
<Item class="ModuleScript" referent="RBX625EEDF3B809470F94D6CA72BD468ED6">
|
|
413
|
+
<Properties>
|
|
414
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
415
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
416
|
+
if #ctx.recentLogs == 0 then
|
|
417
|
+
return { status = "success", result = {"Console kosong. Tidak ada error atau log terbaru."} }
|
|
418
|
+
else
|
|
419
|
+
return { status = "success", result = ctx.recentLogs }
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
]]></ProtectedString>
|
|
423
|
+
<string name="ScriptGuid">{5A11F6E8-8B4E-4561-AF76-62C40C81F0D7}</string>
|
|
424
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
425
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
426
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
427
|
+
<string name="Name">get_logs</string>
|
|
428
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
429
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
430
|
+
</Properties>
|
|
431
|
+
</Item>
|
|
432
|
+
<Item class="ModuleScript" referent="RBXE5B71E01ACBE4AD0B6DC072381E6F57B">
|
|
433
|
+
<Properties>
|
|
434
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
435
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
436
|
+
if targetInstance:IsA("LuaSourceContainer") then
|
|
437
|
+
return { status = "success", result = targetInstance.Source }
|
|
438
|
+
else
|
|
439
|
+
return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. "). Kamu WAJIB menggunakan get_children terlebih dahulu sebelum menarik source." }
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
]]></ProtectedString>
|
|
443
|
+
<string name="ScriptGuid">{D6848696-9F37-490E-9ADF-634A868F8B0A}</string>
|
|
444
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
445
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
446
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
447
|
+
<string name="Name">get_script_source</string>
|
|
448
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
449
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
450
|
+
</Properties>
|
|
451
|
+
</Item>
|
|
452
|
+
<Item class="ModuleScript" referent="RBX8A29B2C0C1804D6A9A8C3DEBD337010E">
|
|
453
|
+
<Properties>
|
|
454
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
455
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
456
|
+
local InsertService = game:GetService("InsertService")
|
|
457
|
+
local assetId = tonumber(data)
|
|
458
|
+
if not assetId then
|
|
459
|
+
return { status = "error", error = "Asset ID harus berupa angka yang valid." }
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
local success, model = pcall(function()
|
|
463
|
+
return InsertService:LoadAsset(assetId)
|
|
464
|
+
end)
|
|
465
|
+
|
|
466
|
+
if success and model then
|
|
467
|
+
model.Parent = game.Workspace
|
|
468
|
+
return { status = "success", result = "Aset berhasil dimasukkan ke Workspace." }
|
|
469
|
+
else
|
|
470
|
+
return { status = "error", error = "Gagal memuat aset. Pastikan Asset ID benar dan akun/plugin Anda memiliki izin (Ownership/Public)." }
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
]]></ProtectedString>
|
|
474
|
+
<string name="ScriptGuid">{7C5C84D5-FCBE-403F-9679-BF4A782C4A82}</string>
|
|
475
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
476
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
477
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
478
|
+
<string name="Name">insert_asset</string>
|
|
479
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
480
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
481
|
+
</Properties>
|
|
482
|
+
</Item>
|
|
483
|
+
<Item class="ModuleScript" referent="RBXB2C4DDC5A9A84FC5B6318F52E35AF483">
|
|
484
|
+
<Properties>
|
|
485
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
486
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
487
|
+
if targetInstance:IsA("LuaSourceContainer") then
|
|
488
|
+
local prevSource = ctx.scriptHistory[targetPath]
|
|
489
|
+
if prevSource then
|
|
490
|
+
targetInstance.Source = prevSource
|
|
491
|
+
return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil di-rollback ke versi sebelumnya." }
|
|
492
|
+
else
|
|
493
|
+
return { status = "error", error = "Tidak ada riwayat backup sebelumnya di sesi Studio ini untuk: " .. targetPath }
|
|
494
|
+
end
|
|
495
|
+
else
|
|
496
|
+
return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
]]></ProtectedString>
|
|
500
|
+
<string name="ScriptGuid">{C7FD1B40-5FDA-4700-AE2A-201A6D1E282E}</string>
|
|
501
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
502
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
503
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
504
|
+
<string name="Name">rollback_script</string>
|
|
505
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
506
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
507
|
+
</Properties>
|
|
508
|
+
</Item>
|
|
509
|
+
<Item class="ModuleScript" referent="RBX4A79E3095F604AFEB115D7444BDB20B3">
|
|
510
|
+
<Properties>
|
|
511
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
512
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
513
|
+
local searchName = tostring(data)
|
|
514
|
+
local results = {}
|
|
515
|
+
|
|
516
|
+
local searchableServices = {game:GetService("Workspace"), game:GetService("ReplicatedStorage"), game:GetService("ServerScriptService"), game:GetService("StarterGui")}
|
|
517
|
+
|
|
518
|
+
for _, service in ipairs(searchableServices) do
|
|
519
|
+
for _, desc in ipairs(service:GetDescendants()) do
|
|
520
|
+
if desc.Name == searchName then
|
|
521
|
+
table.insert(results, desc:GetFullName())
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
if #results > 0 then
|
|
527
|
+
return { status = "success", result = results }
|
|
528
|
+
else
|
|
529
|
+
return { status = "success", result = "Tidak ditemukan instance dengan nama: " .. searchName }
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
]]></ProtectedString>
|
|
533
|
+
<string name="ScriptGuid">{2A73242D-0CFB-4307-861B-CDAAC6A0B3AD}</string>
|
|
534
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
535
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
536
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
537
|
+
<string name="Name">search_instance</string>
|
|
538
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
539
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
540
|
+
</Properties>
|
|
541
|
+
</Item>
|
|
542
|
+
<Item class="ModuleScript" referent="RBX543373412A5740FCB6655A9B57FBE9D7">
|
|
543
|
+
<Properties>
|
|
544
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
545
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
546
|
+
if targetInstance:IsA("LuaSourceContainer") then
|
|
547
|
+
local newCode = data or ""
|
|
548
|
+
|
|
549
|
+
-- Catatan: Validasi AST menggunakan loadstring() sengaja DIHAPUS.
|
|
550
|
+
-- Karena secara default Roblox Studio mematikan LoadStringEnabled, menggunakannya
|
|
551
|
+
-- hanya akan membuat AI gagal terus menerus untuk menulis script.
|
|
552
|
+
-- Error syntax akan ditangkap secara native oleh Roblox Output.
|
|
553
|
+
|
|
554
|
+
ctx.scriptHistory[targetPath] = targetInstance.Source
|
|
555
|
+
targetInstance.Source = newCode
|
|
556
|
+
return { status = "success", result = "Script '" .. targetInstance.Name .. "' berhasil diperbarui." }
|
|
557
|
+
else
|
|
558
|
+
return { status = "error", error = "Target BUKAN Script (ClassName: " .. targetInstance.ClassName .. ")." }
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
]]></ProtectedString>
|
|
562
|
+
<string name="ScriptGuid">{B2F31812-C6C6-4AF9-8320-95D1E9069768}</string>
|
|
563
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
564
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
565
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
566
|
+
<string name="Name">update_script_source</string>
|
|
567
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
568
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
569
|
+
</Properties>
|
|
570
|
+
</Item>
|
|
571
|
+
<Item class="ModuleScript" referent="RBX67FD07EDDECD4543B003A3628AF30B03">
|
|
572
|
+
<Properties>
|
|
573
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
574
|
+
<ProtectedString name="Source"><![CDATA[local UIManager = {}
|
|
575
|
+
local widget = nil
|
|
576
|
+
|
|
577
|
+
function UIManager.init(pluginInstance, studioId)
|
|
578
|
+
-- Membuat Widget Panel yang bisa di-dock (menempel)
|
|
579
|
+
local widgetInfo = DockWidgetPluginGuiInfo.new(
|
|
580
|
+
Enum.InitialDockState.Right,
|
|
581
|
+
false, -- Initially hidden until toolbar button is clicked
|
|
582
|
+
false, -- Override enabled
|
|
583
|
+
380, -- Default width
|
|
584
|
+
520, -- Default height
|
|
585
|
+
280, -- Min width
|
|
586
|
+
320 -- Min height
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
widget = pluginInstance:CreateDockWidgetPluginGui("NVStudioMCP_GUI_V3", widgetInfo)
|
|
590
|
+
widget.Title = "NVStudio MCP Dashboard"
|
|
591
|
+
|
|
592
|
+
-- ═══════════════════════════════════════════
|
|
593
|
+
-- ROOT FRAME (Non-scrolling, fills entire widget)
|
|
594
|
+
-- ═══════════════════════════════════════════
|
|
595
|
+
local rootFrame = Instance.new("Frame")
|
|
596
|
+
rootFrame.Size = UDim2.new(1, 0, 1, 0)
|
|
597
|
+
rootFrame.BackgroundColor3 = Color3.fromRGB(22, 22, 26)
|
|
598
|
+
rootFrame.BorderSizePixel = 0
|
|
599
|
+
rootFrame.Parent = widget
|
|
600
|
+
|
|
601
|
+
local rootPadding = Instance.new("UIPadding")
|
|
602
|
+
rootPadding.PaddingTop = UDim.new(0, 8)
|
|
603
|
+
rootPadding.PaddingLeft = UDim.new(0, 10)
|
|
604
|
+
rootPadding.PaddingRight = UDim.new(0, 10)
|
|
605
|
+
rootPadding.PaddingBottom = UDim.new(0, 8)
|
|
606
|
+
rootPadding.Parent = rootFrame
|
|
607
|
+
|
|
608
|
+
local rootLayout = Instance.new("UIListLayout")
|
|
609
|
+
rootLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
610
|
+
rootLayout.Padding = UDim.new(0, 6)
|
|
611
|
+
rootLayout.Parent = rootFrame
|
|
612
|
+
|
|
613
|
+
-- ═══════════════════════════════════════════
|
|
614
|
+
-- SECTION 1: HEADER CARD
|
|
615
|
+
-- ═══════════════════════════════════════════
|
|
616
|
+
local headerFrame = Instance.new("Frame")
|
|
617
|
+
headerFrame.AutomaticSize = Enum.AutomaticSize.Y
|
|
618
|
+
headerFrame.Size = UDim2.new(1, 0, 0, 0)
|
|
619
|
+
headerFrame.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
|
|
620
|
+
headerFrame.BorderSizePixel = 0
|
|
621
|
+
headerFrame.LayoutOrder = 1
|
|
622
|
+
headerFrame.Parent = rootFrame
|
|
623
|
+
|
|
624
|
+
local hCorner = Instance.new("UICorner")
|
|
625
|
+
hCorner.CornerRadius = UDim.new(0, 6)
|
|
626
|
+
hCorner.Parent = headerFrame
|
|
627
|
+
|
|
628
|
+
local hPadding = Instance.new("UIPadding")
|
|
629
|
+
hPadding.PaddingTop = UDim.new(0, 10)
|
|
630
|
+
hPadding.PaddingLeft = UDim.new(0, 12)
|
|
631
|
+
hPadding.PaddingRight = UDim.new(0, 12)
|
|
632
|
+
hPadding.PaddingBottom = UDim.new(0, 10)
|
|
633
|
+
hPadding.Parent = headerFrame
|
|
634
|
+
|
|
635
|
+
local hLayout = Instance.new("UIListLayout")
|
|
636
|
+
hLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
637
|
+
hLayout.Padding = UDim.new(0, 2)
|
|
638
|
+
hLayout.Parent = headerFrame
|
|
639
|
+
|
|
640
|
+
-- Title Row (Name + Status side by side)
|
|
641
|
+
local titleRow = Instance.new("Frame")
|
|
642
|
+
titleRow.AutomaticSize = Enum.AutomaticSize.Y
|
|
643
|
+
titleRow.Size = UDim2.new(1, 0, 0, 0)
|
|
644
|
+
titleRow.BackgroundTransparency = 1
|
|
645
|
+
titleRow.LayoutOrder = 1
|
|
646
|
+
titleRow.Parent = headerFrame
|
|
647
|
+
|
|
648
|
+
local titleLabel = Instance.new("TextLabel")
|
|
649
|
+
titleLabel.Text = "NVStudio MCP"
|
|
650
|
+
titleLabel.Font = Enum.Font.GothamBold
|
|
651
|
+
titleLabel.TextSize = 18
|
|
652
|
+
titleLabel.TextColor3 = Color3.fromRGB(240, 240, 245)
|
|
653
|
+
titleLabel.BackgroundTransparency = 1
|
|
654
|
+
titleLabel.Size = UDim2.new(0.6, 0, 0, 22)
|
|
655
|
+
titleLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
656
|
+
titleLabel.Parent = titleRow
|
|
657
|
+
|
|
658
|
+
local statusLabel = Instance.new("TextLabel")
|
|
659
|
+
statusLabel.Text = "● Disconnected"
|
|
660
|
+
statusLabel.Font = Enum.Font.GothamBold
|
|
661
|
+
statusLabel.TextSize = 12
|
|
662
|
+
statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
|
|
663
|
+
statusLabel.BackgroundTransparency = 1
|
|
664
|
+
statusLabel.Size = UDim2.new(0.4, 0, 0, 22)
|
|
665
|
+
statusLabel.Position = UDim2.new(0.6, 0, 0, 0)
|
|
666
|
+
statusLabel.TextXAlignment = Enum.TextXAlignment.Right
|
|
667
|
+
statusLabel.Parent = titleRow
|
|
668
|
+
|
|
669
|
+
-- Version
|
|
670
|
+
local versionLabel = Instance.new("TextLabel")
|
|
671
|
+
versionLabel.Text = "v1.2.0 · Hybrid Agent"
|
|
672
|
+
versionLabel.Font = Enum.Font.Gotham
|
|
673
|
+
versionLabel.TextSize = 11
|
|
674
|
+
versionLabel.TextColor3 = Color3.fromRGB(120, 120, 130)
|
|
675
|
+
versionLabel.BackgroundTransparency = 1
|
|
676
|
+
versionLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
677
|
+
versionLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
678
|
+
versionLabel.LayoutOrder = 2
|
|
679
|
+
versionLabel.Parent = headerFrame
|
|
680
|
+
|
|
681
|
+
-- Thin separator
|
|
682
|
+
local sep1 = Instance.new("Frame")
|
|
683
|
+
sep1.Size = UDim2.new(1, 0, 0, 1)
|
|
684
|
+
sep1.BackgroundColor3 = Color3.fromRGB(50, 50, 58)
|
|
685
|
+
sep1.BorderSizePixel = 0
|
|
686
|
+
sep1.LayoutOrder = 3
|
|
687
|
+
sep1.Parent = headerFrame
|
|
688
|
+
|
|
689
|
+
-- Place info
|
|
690
|
+
local placeLabel = Instance.new("TextLabel")
|
|
691
|
+
placeLabel.Text = "📁 " .. (game.Name ~= "" and game.Name or "Untitled Baseplate")
|
|
692
|
+
placeLabel.Font = Enum.Font.Gotham
|
|
693
|
+
placeLabel.TextSize = 11
|
|
694
|
+
placeLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
|
|
695
|
+
placeLabel.BackgroundTransparency = 1
|
|
696
|
+
placeLabel.Size = UDim2.new(1, 0, 0, 16)
|
|
697
|
+
placeLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
698
|
+
placeLabel.LayoutOrder = 4
|
|
699
|
+
placeLabel.Parent = headerFrame
|
|
700
|
+
|
|
701
|
+
-- Studio ID
|
|
702
|
+
local idLabel = Instance.new("TextLabel")
|
|
703
|
+
idLabel.Text = "🔗 " .. string.sub(tostring(studioId), 1, 18) .. "..."
|
|
704
|
+
idLabel.Font = Enum.Font.Code
|
|
705
|
+
idLabel.TextSize = 10
|
|
706
|
+
idLabel.TextColor3 = Color3.fromRGB(90, 180, 230)
|
|
707
|
+
idLabel.BackgroundTransparency = 1
|
|
708
|
+
idLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
709
|
+
idLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
710
|
+
idLabel.LayoutOrder = 5
|
|
711
|
+
idLabel.Parent = headerFrame
|
|
712
|
+
|
|
713
|
+
-- Server URL
|
|
714
|
+
local serverLabel = Instance.new("TextLabel")
|
|
715
|
+
serverLabel.Text = "🌐 localhost:3055"
|
|
716
|
+
serverLabel.Font = Enum.Font.Code
|
|
717
|
+
serverLabel.TextSize = 10
|
|
718
|
+
serverLabel.TextColor3 = Color3.fromRGB(140, 140, 150)
|
|
719
|
+
serverLabel.BackgroundTransparency = 1
|
|
720
|
+
serverLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
721
|
+
serverLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
722
|
+
serverLabel.LayoutOrder = 6
|
|
723
|
+
serverLabel.Parent = headerFrame
|
|
724
|
+
|
|
725
|
+
-- ═══════════════════════════════════════════
|
|
726
|
+
-- SECTION 2: CONTROLS ROW
|
|
727
|
+
-- ═══════════════════════════════════════════
|
|
728
|
+
local controlsFrame = Instance.new("Frame")
|
|
729
|
+
controlsFrame.Size = UDim2.new(1, 0, 0, 32)
|
|
730
|
+
controlsFrame.BackgroundTransparency = 1
|
|
731
|
+
controlsFrame.LayoutOrder = 2
|
|
732
|
+
controlsFrame.Parent = rootFrame
|
|
733
|
+
|
|
734
|
+
-- Connect / Disconnect Button
|
|
735
|
+
local connectBtn = Instance.new("TextButton")
|
|
736
|
+
connectBtn.Size = UDim2.new(0.52, -4, 1, 0)
|
|
737
|
+
connectBtn.Position = UDim2.new(0, 0, 0, 0)
|
|
738
|
+
connectBtn.Text = " Connect"
|
|
739
|
+
connectBtn.Font = Enum.Font.GothamBold
|
|
740
|
+
connectBtn.TextSize = 13
|
|
741
|
+
connectBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
|
|
742
|
+
connectBtn.BackgroundColor3 = Color3.fromRGB(40, 160, 80)
|
|
743
|
+
connectBtn.BorderSizePixel = 0
|
|
744
|
+
connectBtn.AutoButtonColor = false
|
|
745
|
+
connectBtn.ZIndex = 2
|
|
746
|
+
connectBtn.Parent = controlsFrame
|
|
747
|
+
|
|
748
|
+
local cCorner = Instance.new("UICorner")
|
|
749
|
+
cCorner.CornerRadius = UDim.new(0, 5)
|
|
750
|
+
cCorner.Parent = connectBtn
|
|
751
|
+
|
|
752
|
+
-- Hover Effects for Connect Button (menggunakan stored base color, bukan text check)
|
|
753
|
+
local _btnBaseColor = Color3.fromRGB(40, 160, 80)
|
|
754
|
+
local _isHovering = false
|
|
755
|
+
|
|
756
|
+
connectBtn.MouseEnter:Connect(function()
|
|
757
|
+
_isHovering = true
|
|
758
|
+
connectBtn.BackgroundColor3 = Color3.fromRGB(
|
|
759
|
+
math.min(255, math.floor(_btnBaseColor.R * 255 + 25)),
|
|
760
|
+
math.min(255, math.floor(_btnBaseColor.G * 255 + 25)),
|
|
761
|
+
math.min(255, math.floor(_btnBaseColor.B * 255 + 25))
|
|
762
|
+
)
|
|
763
|
+
end)
|
|
764
|
+
connectBtn.MouseLeave:Connect(function()
|
|
765
|
+
_isHovering = false
|
|
766
|
+
connectBtn.BackgroundColor3 = _btnBaseColor
|
|
767
|
+
end)
|
|
768
|
+
|
|
769
|
+
-- Auto-Connect Checkbox Container
|
|
770
|
+
local autoConnectContainer = Instance.new("Frame")
|
|
771
|
+
autoConnectContainer.Size = UDim2.new(0.48, -4, 1, 0)
|
|
772
|
+
autoConnectContainer.Position = UDim2.new(0.52, 4, 0, 0)
|
|
773
|
+
autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
|
|
774
|
+
autoConnectContainer.BorderSizePixel = 0
|
|
775
|
+
autoConnectContainer.Parent = controlsFrame
|
|
776
|
+
|
|
777
|
+
local acCorner = Instance.new("UICorner")
|
|
778
|
+
acCorner.CornerRadius = UDim.new(0, 5)
|
|
779
|
+
acCorner.Parent = autoConnectContainer
|
|
780
|
+
|
|
781
|
+
-- Checkbox visual (outer box)
|
|
782
|
+
local checkboxOuter = Instance.new("Frame")
|
|
783
|
+
checkboxOuter.Size = UDim2.new(0, 16, 0, 16)
|
|
784
|
+
checkboxOuter.Position = UDim2.new(0, 10, 0.5, -8)
|
|
785
|
+
checkboxOuter.BackgroundColor3 = Color3.fromRGB(55, 55, 65)
|
|
786
|
+
checkboxOuter.BorderSizePixel = 0
|
|
787
|
+
checkboxOuter.Parent = autoConnectContainer
|
|
788
|
+
|
|
789
|
+
local cbCorner = Instance.new("UICorner")
|
|
790
|
+
cbCorner.CornerRadius = UDim.new(0, 3)
|
|
791
|
+
cbCorner.Parent = checkboxOuter
|
|
792
|
+
|
|
793
|
+
local cbStroke = Instance.new("UIStroke")
|
|
794
|
+
cbStroke.Color = Color3.fromRGB(80, 80, 95)
|
|
795
|
+
cbStroke.Thickness = 1
|
|
796
|
+
cbStroke.Parent = checkboxOuter
|
|
797
|
+
|
|
798
|
+
-- Checkbox inner (check mark)
|
|
799
|
+
local checkboxInner = Instance.new("Frame")
|
|
800
|
+
checkboxInner.Size = UDim2.new(0, 10, 0, 10)
|
|
801
|
+
checkboxInner.Position = UDim2.new(0.5, -5, 0.5, -5)
|
|
802
|
+
checkboxInner.BackgroundColor3 = Color3.fromRGB(80, 170, 255)
|
|
803
|
+
checkboxInner.Visible = false
|
|
804
|
+
checkboxInner.Parent = checkboxOuter
|
|
805
|
+
|
|
806
|
+
local cbiCorner = Instance.new("UICorner")
|
|
807
|
+
cbiCorner.CornerRadius = UDim.new(0, 2)
|
|
808
|
+
cbiCorner.Parent = checkboxInner
|
|
809
|
+
|
|
810
|
+
-- Label
|
|
811
|
+
local autoConnectLabel = Instance.new("TextLabel")
|
|
812
|
+
autoConnectLabel.Text = "Auto-Connect"
|
|
813
|
+
autoConnectLabel.Font = Enum.Font.Gotham
|
|
814
|
+
autoConnectLabel.TextSize = 11
|
|
815
|
+
autoConnectLabel.TextColor3 = Color3.fromRGB(180, 180, 190)
|
|
816
|
+
autoConnectLabel.BackgroundTransparency = 1
|
|
817
|
+
autoConnectLabel.Position = UDim2.new(0, 32, 0, 0)
|
|
818
|
+
autoConnectLabel.Size = UDim2.new(1, -36, 1, 0)
|
|
819
|
+
autoConnectLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
820
|
+
autoConnectLabel.Parent = autoConnectContainer
|
|
821
|
+
|
|
822
|
+
-- Invisible overlay button for checkbox hit area
|
|
823
|
+
local autoConnectBtn = Instance.new("TextButton")
|
|
824
|
+
autoConnectBtn.Size = UDim2.new(1, 0, 1, 0)
|
|
825
|
+
autoConnectBtn.BackgroundTransparency = 1
|
|
826
|
+
autoConnectBtn.Text = ""
|
|
827
|
+
autoConnectBtn.ZIndex = 3 -- Di atas elemen visual agar klik selalu terdeteksi
|
|
828
|
+
autoConnectBtn.Parent = autoConnectContainer
|
|
829
|
+
|
|
830
|
+
-- Hover effect for checkbox container
|
|
831
|
+
autoConnectBtn.MouseEnter:Connect(function()
|
|
832
|
+
autoConnectContainer.BackgroundColor3 = Color3.fromRGB(42, 42, 50)
|
|
833
|
+
end)
|
|
834
|
+
autoConnectBtn.MouseLeave:Connect(function()
|
|
835
|
+
autoConnectContainer.BackgroundColor3 = Color3.fromRGB(32, 32, 38)
|
|
836
|
+
end)
|
|
837
|
+
|
|
838
|
+
-- ═══════════════════════════════════════════
|
|
839
|
+
-- SECTION 3: TERMINAL LOG (fills remaining space)
|
|
840
|
+
-- ═══════════════════════════════════════════
|
|
841
|
+
-- Terminal Label
|
|
842
|
+
local termLabel = Instance.new("TextLabel")
|
|
843
|
+
termLabel.Text = "TERMINAL"
|
|
844
|
+
termLabel.Font = Enum.Font.GothamBold
|
|
845
|
+
termLabel.TextSize = 10
|
|
846
|
+
termLabel.TextColor3 = Color3.fromRGB(90, 90, 100)
|
|
847
|
+
termLabel.BackgroundTransparency = 1
|
|
848
|
+
termLabel.Size = UDim2.new(1, 0, 0, 14)
|
|
849
|
+
termLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
850
|
+
termLabel.LayoutOrder = 3
|
|
851
|
+
termLabel.Parent = rootFrame
|
|
852
|
+
|
|
853
|
+
-- Terminal container: Fills ALL remaining vertical space
|
|
854
|
+
local terminalContainer = Instance.new("Frame")
|
|
855
|
+
terminalContainer.BackgroundColor3 = Color3.fromRGB(14, 14, 18)
|
|
856
|
+
terminalContainer.BorderSizePixel = 0
|
|
857
|
+
terminalContainer.LayoutOrder = 4
|
|
858
|
+
terminalContainer.ClipsDescendants = true
|
|
859
|
+
terminalContainer.Parent = rootFrame
|
|
860
|
+
|
|
861
|
+
local termCorner = Instance.new("UICorner")
|
|
862
|
+
termCorner.CornerRadius = UDim.new(0, 5)
|
|
863
|
+
termCorner.Parent = terminalContainer
|
|
864
|
+
|
|
865
|
+
local termStroke = Instance.new("UIStroke")
|
|
866
|
+
termStroke.Color = Color3.fromRGB(40, 40, 48)
|
|
867
|
+
termStroke.Thickness = 1
|
|
868
|
+
termStroke.Parent = terminalContainer
|
|
869
|
+
|
|
870
|
+
-- Scrolling log area inside terminal
|
|
871
|
+
local scrollingFrame = Instance.new("ScrollingFrame")
|
|
872
|
+
scrollingFrame.Size = UDim2.new(1, -8, 1, -4)
|
|
873
|
+
scrollingFrame.Position = UDim2.new(0, 4, 0, 2)
|
|
874
|
+
scrollingFrame.BackgroundTransparency = 1
|
|
875
|
+
scrollingFrame.BorderSizePixel = 0
|
|
876
|
+
scrollingFrame.ScrollBarThickness = 4
|
|
877
|
+
scrollingFrame.ScrollBarImageColor3 = Color3.fromRGB(70, 70, 85)
|
|
878
|
+
scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
|
|
879
|
+
scrollingFrame.Parent = terminalContainer
|
|
880
|
+
|
|
881
|
+
local listLayout = Instance.new("UIListLayout")
|
|
882
|
+
listLayout.Padding = UDim.new(0, 1)
|
|
883
|
+
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
|
884
|
+
listLayout.Parent = scrollingFrame
|
|
885
|
+
|
|
886
|
+
-- ═══════════════════════════════════════════
|
|
887
|
+
-- RESPONSIVITY: Dynamically size terminal to fill remaining space
|
|
888
|
+
-- ═══════════════════════════════════════════
|
|
889
|
+
local function updateLayout()
|
|
890
|
+
-- Calculate how much space header + controls + termLabel use
|
|
891
|
+
local usedHeight = rootLayout.AbsoluteContentSize.Y - terminalContainer.AbsoluteSize.Y
|
|
892
|
+
local availableHeight = rootFrame.AbsoluteSize.Y - 16 -- rootPadding top+bottom
|
|
893
|
+
local termHeight = math.max(100, availableHeight - usedHeight)
|
|
894
|
+
terminalContainer.Size = UDim2.new(1, 0, 0, termHeight)
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
-- Fire on widget resize
|
|
898
|
+
widget:GetPropertyChangedSignal("AbsoluteSize"):Connect(updateLayout)
|
|
899
|
+
-- Also fire once after initial render
|
|
900
|
+
task.defer(updateLayout)
|
|
901
|
+
|
|
902
|
+
-- ═══════════════════════════════════════════
|
|
903
|
+
-- Store references for external access
|
|
904
|
+
-- ═══════════════════════════════════════════
|
|
905
|
+
UIManager.widget = widget
|
|
906
|
+
UIManager.connectBtn = connectBtn
|
|
907
|
+
UIManager.autoConnectBtn = autoConnectBtn
|
|
908
|
+
UIManager.checkboxInner = checkboxInner
|
|
909
|
+
UIManager.checkboxStroke = cbStroke
|
|
910
|
+
UIManager.statusLabel = statusLabel
|
|
911
|
+
UIManager.scrollingFrame = scrollingFrame
|
|
912
|
+
UIManager.listLayout = listLayout
|
|
913
|
+
UIManager.terminalContainer = terminalContainer
|
|
914
|
+
UIManager.termStroke = termStroke
|
|
915
|
+
UIManager.logsCount = 0
|
|
916
|
+
|
|
917
|
+
-- Simpan referensi hover state agar setStatus bisa update base color
|
|
918
|
+
UIManager._btnBaseColor = _btnBaseColor
|
|
919
|
+
UIManager._isHovering = _isHovering
|
|
920
|
+
UIManager._updateBtnBaseColor = function(newColor)
|
|
921
|
+
_btnBaseColor = newColor
|
|
922
|
+
if not _isHovering then
|
|
923
|
+
connectBtn.BackgroundColor3 = newColor
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
return UIManager
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
-- ═══════════════════════════════════════════
|
|
931
|
+
-- Menambahkan baris log baru ke terminal GUI
|
|
932
|
+
-- ═══════════════════════════════════════════
|
|
933
|
+
function UIManager:addLog(text, color)
|
|
934
|
+
local timestamp = os.date("%H:%M:%S")
|
|
935
|
+
local displayText = "[" .. timestamp .. "] " .. text
|
|
936
|
+
|
|
937
|
+
local logLabel = Instance.new("TextLabel")
|
|
938
|
+
logLabel.Text = displayText
|
|
939
|
+
logLabel.Font = Enum.Font.Code
|
|
940
|
+
logLabel.TextSize = 11
|
|
941
|
+
logLabel.TextColor3 = color or Color3.fromRGB(180, 180, 190)
|
|
942
|
+
logLabel.BackgroundTransparency = 1
|
|
943
|
+
logLabel.TextWrapped = true
|
|
944
|
+
logLabel.TextXAlignment = Enum.TextXAlignment.Left
|
|
945
|
+
logLabel.RichText = false
|
|
946
|
+
logLabel.AutomaticSize = Enum.AutomaticSize.Y
|
|
947
|
+
logLabel.Size = UDim2.new(1, -6, 0, 0) -- Width fill, height auto
|
|
948
|
+
|
|
949
|
+
local logPadding = Instance.new("UIPadding")
|
|
950
|
+
logPadding.PaddingLeft = UDim.new(0, 4)
|
|
951
|
+
logPadding.PaddingTop = UDim.new(0, 1)
|
|
952
|
+
logPadding.PaddingBottom = UDim.new(0, 1)
|
|
953
|
+
logPadding.Parent = logLabel
|
|
954
|
+
|
|
955
|
+
logLabel.LayoutOrder = self.logsCount
|
|
956
|
+
logLabel.Parent = self.scrollingFrame
|
|
957
|
+
self.logsCount = self.logsCount + 1
|
|
958
|
+
|
|
959
|
+
-- Limit max logs to prevent memory bloat
|
|
960
|
+
if self.logsCount > 150 then
|
|
961
|
+
local first = self.scrollingFrame:FindFirstChildWhichIsA("TextLabel")
|
|
962
|
+
if first then first:Destroy() end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
-- Update canvas size after a frame so AutomaticSize has resolved
|
|
966
|
+
task.defer(function()
|
|
967
|
+
self.scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, self.listLayout.AbsoluteContentSize.Y + 4)
|
|
968
|
+
self.scrollingFrame.CanvasPosition = Vector2.new(0, math.max(0, self.listLayout.AbsoluteContentSize.Y - self.scrollingFrame.AbsoluteSize.Y))
|
|
969
|
+
end)
|
|
970
|
+
end
|
|
971
|
+
|
|
972
|
+
-- ═══════════════════════════════════════════
|
|
973
|
+
-- 3 State Setter (Disconnected, Waiting, Connected)
|
|
974
|
+
-- ═══════════════════════════════════════════
|
|
975
|
+
function UIManager:setStatus(stateStr)
|
|
976
|
+
if stateStr == "disconnected" then
|
|
977
|
+
self.connectBtn.Text = " Connect"
|
|
978
|
+
self._updateBtnBaseColor(Color3.fromRGB(40, 160, 80))
|
|
979
|
+
self.statusLabel.Text = "● Disconnected"
|
|
980
|
+
self.statusLabel.TextColor3 = Color3.fromRGB(255, 100, 100)
|
|
981
|
+
self.termStroke.Color = Color3.fromRGB(40, 40, 48)
|
|
982
|
+
|
|
983
|
+
elseif stateStr == "waiting" then
|
|
984
|
+
self.connectBtn.Text = " Disconnect"
|
|
985
|
+
self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
|
|
986
|
+
self.statusLabel.Text = "● Waiting for AI..."
|
|
987
|
+
self.statusLabel.TextColor3 = Color3.fromRGB(255, 200, 80)
|
|
988
|
+
self.termStroke.Color = Color3.fromRGB(130, 110, 50)
|
|
989
|
+
|
|
990
|
+
elseif stateStr == "connected" then
|
|
991
|
+
self.connectBtn.Text = " Disconnect"
|
|
992
|
+
self._updateBtnBaseColor(Color3.fromRGB(180, 50, 50))
|
|
993
|
+
self.statusLabel.Text = "● Connected"
|
|
994
|
+
self.statusLabel.TextColor3 = Color3.fromRGB(100, 230, 120)
|
|
995
|
+
self.termStroke.Color = Color3.fromRGB(50, 120, 60)
|
|
996
|
+
end
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
-- Mengubah visual custom checkbox
|
|
1000
|
+
function UIManager:setAutoConnectState(isAuto)
|
|
1001
|
+
self.checkboxInner.Visible = isAuto
|
|
1002
|
+
if isAuto then
|
|
1003
|
+
self.checkboxStroke.Color = Color3.fromRGB(80, 170, 255)
|
|
1004
|
+
else
|
|
1005
|
+
self.checkboxStroke.Color = Color3.fromRGB(80, 80, 95)
|
|
1006
|
+
end
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
-- Sakelar visibilitas panel GUI
|
|
1010
|
+
function UIManager:toggleVisibility()
|
|
1011
|
+
if self.widget then
|
|
1012
|
+
self.widget.Enabled = not self.widget.Enabled
|
|
1013
|
+
end
|
|
1014
|
+
end
|
|
1015
|
+
|
|
1016
|
+
return UIManager
|
|
1017
|
+
]]></ProtectedString>
|
|
1018
|
+
<string name="ScriptGuid">{8B113EA4-CA11-4BDF-BFA7-98B5E077AC2E}</string>
|
|
1019
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1020
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1021
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1022
|
+
<string name="Name">ui_manager</string>
|
|
1023
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
1024
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
1025
|
+
</Properties>
|
|
1026
|
+
</Item>
|
|
1027
|
+
<Item class="ModuleScript" referent="RBX6852D813E05543C487103F791CF924EF">
|
|
1028
|
+
<Properties>
|
|
1029
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
1030
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
1031
|
+
local code = data
|
|
1032
|
+
if not code or code == "" then
|
|
1033
|
+
return { status = "error", error = "Kode yang dieksekusi tidak boleh kosong." }
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
local HttpService = game:GetService("HttpService")
|
|
1037
|
+
|
|
1038
|
+
-- Bungkus code ke dalam fungsi return
|
|
1039
|
+
local wrappedCode = "return function()\n" .. code .. "\nend"
|
|
1040
|
+
|
|
1041
|
+
local moduleScript = Instance.new("ModuleScript")
|
|
1042
|
+
moduleScript.Name = "AI_Execute_Temp"
|
|
1043
|
+
moduleScript.Source = wrappedCode
|
|
1044
|
+
|
|
1045
|
+
-- Taruh di ReplicatedStorage sementara agar bisa di-require
|
|
1046
|
+
moduleScript.Parent = game:GetService("ReplicatedStorage")
|
|
1047
|
+
|
|
1048
|
+
local resultData = nil
|
|
1049
|
+
local execError = nil
|
|
1050
|
+
local isFinished = false
|
|
1051
|
+
|
|
1052
|
+
-- Eksekusi asinkron dengan batas waktu (timeout) 5 detik
|
|
1053
|
+
task.spawn(function()
|
|
1054
|
+
local reqSuccess, func = pcall(function() return require(moduleScript) end)
|
|
1055
|
+
if reqSuccess and type(func) == "function" then
|
|
1056
|
+
local execSuccess, res = pcall(func)
|
|
1057
|
+
if execSuccess then
|
|
1058
|
+
resultData = res
|
|
1059
|
+
else
|
|
1060
|
+
execError = "Runtime Error: " .. tostring(res)
|
|
1061
|
+
end
|
|
1062
|
+
else
|
|
1063
|
+
execError = "Syntax/Require Error: " .. tostring(func)
|
|
1064
|
+
end
|
|
1065
|
+
isFinished = true
|
|
1066
|
+
end)
|
|
1067
|
+
|
|
1068
|
+
-- Tunggu hingga 5 detik
|
|
1069
|
+
local timeWaited = 0
|
|
1070
|
+
while not isFinished and timeWaited < 5 do
|
|
1071
|
+
timeWaited = timeWaited + task.wait()
|
|
1072
|
+
end
|
|
1073
|
+
|
|
1074
|
+
-- Pembersihan (Wajib dihancurkan)
|
|
1075
|
+
pcall(function() moduleScript:Destroy() end)
|
|
1076
|
+
|
|
1077
|
+
if not isFinished then
|
|
1078
|
+
return { status = "error", error = "Timeout 5 detik terlampaui. Kemungkinan ada infinite loop tanpa task.wait()." }
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
if execError then
|
|
1082
|
+
return { status = "error", error = execError }
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
-- Cegah return nil error
|
|
1086
|
+
if resultData == nil then
|
|
1087
|
+
return { status = "success", result = "Eksekusi berhasil (Tidak ada nilai kembalian)." }
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
-- JSON Encode untuk mencegah raw Instance return crash
|
|
1091
|
+
local serializeSuccess, jsonRes = pcall(function()
|
|
1092
|
+
return HttpService:JSONEncode(resultData)
|
|
1093
|
+
end)
|
|
1094
|
+
|
|
1095
|
+
if serializeSuccess then
|
|
1096
|
+
return { status = "success", result = HttpService:JSONDecode(jsonRes) } -- decode kembali agar format aslinya diteruskan oleh router
|
|
1097
|
+
else
|
|
1098
|
+
return { status = "error", error = "Gagal menserialisasi hasil (Dilarang mengembalikan raw Roblox Instance): " .. tostring(jsonRes) }
|
|
1099
|
+
end
|
|
1100
|
+
end
|
|
1101
|
+
]]></ProtectedString>
|
|
1102
|
+
<string name="ScriptGuid">{E656AA51-AC95-4EED-9C73-CA164FE1FA52}</string>
|
|
1103
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1104
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1105
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1106
|
+
<string name="Name">execute_luau</string>
|
|
1107
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
1108
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
1109
|
+
</Properties>
|
|
1110
|
+
</Item>
|
|
1111
|
+
<Item class="ModuleScript" referent="RBX87CCA7F1F7C047D69399148376B5D38E">
|
|
1112
|
+
<Properties>
|
|
1113
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
1114
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
1115
|
+
-- Guardrail: Cegah penghapusan Service utama
|
|
1116
|
+
local protectedServices = {
|
|
1117
|
+
"Workspace", "Players", "Lighting", "MaterialService", "NetworkClient",
|
|
1118
|
+
"ReplicatedFirst", "ReplicatedStorage", "ServerScriptService", "ServerStorage",
|
|
1119
|
+
"StarterGui", "StarterPack", "StarterPlayer", "Teams", "SoundService", "Chat",
|
|
1120
|
+
"TextChatService", "VoiceChatService"
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if targetInstance.Parent == game or targetInstance.Parent == nil then
|
|
1124
|
+
for _, serviceName in ipairs(protectedServices) do
|
|
1125
|
+
if targetInstance.Name == serviceName or targetInstance.ClassName == serviceName then
|
|
1126
|
+
return { status = "error", error = "GUARDRAIL BLOCKED: Anda tidak diizinkan untuk menghapus layanan tingkat atas (" .. targetInstance.Name .. ")." }
|
|
1127
|
+
end
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
local name = targetInstance.Name
|
|
1132
|
+
local className = targetInstance.ClassName
|
|
1133
|
+
local parentName = targetInstance.Parent and targetInstance.Parent.Name or "nil"
|
|
1134
|
+
|
|
1135
|
+
local success, err = pcall(function()
|
|
1136
|
+
targetInstance:Destroy()
|
|
1137
|
+
end)
|
|
1138
|
+
|
|
1139
|
+
if success then
|
|
1140
|
+
return { status = "success", result = "Instance '" .. name .. "' (" .. className .. ") berhasil dihapus dari " .. parentName .. "." }
|
|
1141
|
+
else
|
|
1142
|
+
return { status = "error", error = "Gagal menghapus instance: " .. tostring(err) }
|
|
1143
|
+
end
|
|
1144
|
+
end
|
|
1145
|
+
]]></ProtectedString>
|
|
1146
|
+
<string name="ScriptGuid">{EBD3665C-2719-4288-930C-3902EFF4CCB6}</string>
|
|
1147
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1148
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1149
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1150
|
+
<string name="Name">delete_instance</string>
|
|
1151
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
1152
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
1153
|
+
</Properties>
|
|
1154
|
+
</Item>
|
|
1155
|
+
<Item class="ModuleScript" referent="RBX57ADCE105E57454897B1666DA1D52563">
|
|
1156
|
+
<Properties>
|
|
1157
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
1158
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
1159
|
+
local success, config = pcall(function() return ctx.HttpService:JSONDecode(data) end)
|
|
1160
|
+
if not success or not config.ClassName then
|
|
1161
|
+
return { status = "error", error = "Format data JSON tidak valid atau ClassName hilang." }
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
local newSuccess, newInst = pcall(function()
|
|
1165
|
+
local inst = Instance.new(config.ClassName)
|
|
1166
|
+
inst.Parent = targetInstance
|
|
1167
|
+
|
|
1168
|
+
if config.Properties then
|
|
1169
|
+
for prop, val in pairs(config.Properties) do
|
|
1170
|
+
pcall(function() inst[prop] = val end)
|
|
1171
|
+
end
|
|
1172
|
+
end
|
|
1173
|
+
return inst
|
|
1174
|
+
end)
|
|
1175
|
+
|
|
1176
|
+
if newSuccess then
|
|
1177
|
+
return { status = "success", result = "Instance '"..config.ClassName.."' berhasil dibuat di " .. targetPath }
|
|
1178
|
+
else
|
|
1179
|
+
return { status = "error", error = "Gagal membuat Instance. Pastikan ClassName valid." }
|
|
1180
|
+
end
|
|
1181
|
+
end
|
|
1182
|
+
]]></ProtectedString>
|
|
1183
|
+
<string name="ScriptGuid">{4F0529C1-A158-4652-B56B-A8B224EF87F5}</string>
|
|
1184
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1185
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1186
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1187
|
+
<string name="Name">create_instance</string>
|
|
1188
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
1189
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
1190
|
+
</Properties>
|
|
1191
|
+
</Item>
|
|
1192
|
+
<Item class="ModuleScript" referent="RBXCA3AFE3B00224D6686C665EF3D5A6DAA">
|
|
1193
|
+
<Properties>
|
|
1194
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
1195
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
1196
|
+
local CollectionService = game:GetService("CollectionService")
|
|
1197
|
+
|
|
1198
|
+
local props = {
|
|
1199
|
+
Name = targetInstance.Name,
|
|
1200
|
+
ClassName = targetInstance.ClassName,
|
|
1201
|
+
Parent = targetInstance.Parent and targetInstance.Parent.Name or "nil",
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
-- Ambil Properties Umum secara aman via pcall
|
|
1205
|
+
local function tryProp(propName)
|
|
1206
|
+
local success, val = pcall(function() return targetInstance[propName] end)
|
|
1207
|
+
if success and val ~= nil then
|
|
1208
|
+
if typeof(val) == "Vector3" then
|
|
1209
|
+
props[propName] = {math.round(val.X*100)/100, math.round(val.Y*100)/100, math.round(val.Z*100)/100}
|
|
1210
|
+
elseif typeof(val) == "Color3" then
|
|
1211
|
+
props[propName] = {math.round(val.R * 255), math.round(val.G * 255), math.round(val.B * 255)}
|
|
1212
|
+
elseif typeof(val) == "CFrame" then
|
|
1213
|
+
props[propName] = tostring(val)
|
|
1214
|
+
elseif typeof(val) == "EnumItem" then
|
|
1215
|
+
props[propName] = val.Name
|
|
1216
|
+
elseif typeof(val) == "UDim2" then
|
|
1217
|
+
props[propName] = {val.X.Scale, val.X.Offset, val.Y.Scale, val.Y.Offset}
|
|
1218
|
+
elseif typeof(val) == "Instance" then
|
|
1219
|
+
props[propName] = val:GetFullName()
|
|
1220
|
+
else
|
|
1221
|
+
props[propName] = val
|
|
1222
|
+
end
|
|
1223
|
+
end
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
local commonProps = {
|
|
1227
|
+
"Anchored", "CanCollide", "Transparency", "Reflectance",
|
|
1228
|
+
"Position", "Size", "Color", "Material", "Text", "TextColor3",
|
|
1229
|
+
"BackgroundTransparency", "BackgroundColor3", "Visible", "Enabled",
|
|
1230
|
+
"Volume", "Playing", "Looped", "Value"
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
for _, prop in ipairs(commonProps) do
|
|
1234
|
+
tryProp(prop)
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
-- Attributes & Tags
|
|
1238
|
+
pcall(function()
|
|
1239
|
+
props.Attributes = targetInstance:GetAttributes()
|
|
1240
|
+
end)
|
|
1241
|
+
pcall(function()
|
|
1242
|
+
props.Tags = CollectionService:GetTags(targetInstance)
|
|
1243
|
+
end)
|
|
1244
|
+
|
|
1245
|
+
return { status = "success", result = props }
|
|
1246
|
+
end
|
|
1247
|
+
]]></ProtectedString>
|
|
1248
|
+
<string name="ScriptGuid">{3B18E6FF-1FFC-4B48-936D-8471CB3820C6}</string>
|
|
1249
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1250
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1251
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1252
|
+
<string name="Name">get_properties</string>
|
|
1253
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
1254
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
1255
|
+
</Properties>
|
|
1256
|
+
</Item>
|
|
1257
|
+
<Item class="ModuleScript" referent="RBXD0E8AE88B6B24ECE9F916D7EFC8F978E">
|
|
1258
|
+
<Properties>
|
|
1259
|
+
<Content name="LinkedSource"><null></null></Content>
|
|
1260
|
+
<ProtectedString name="Source"><![CDATA[return function(targetInstance, data, ctx, targetPath)
|
|
1261
|
+
local query = tostring(data)
|
|
1262
|
+
if not query or query == "" then
|
|
1263
|
+
return { status = "error", error = "Query pencarian tidak boleh kosong." }
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1266
|
+
local matches = {}
|
|
1267
|
+
local matchCount = 0
|
|
1268
|
+
local MAX_MATCHES = 50
|
|
1269
|
+
|
|
1270
|
+
local searchableServices = {
|
|
1271
|
+
game:GetService("Workspace"),
|
|
1272
|
+
game:GetService("ServerScriptService"),
|
|
1273
|
+
game:GetService("ReplicatedStorage"),
|
|
1274
|
+
game:GetService("StarterPlayer"),
|
|
1275
|
+
game:GetService("StarterGui")
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
local function searchInScript(scriptObj)
|
|
1279
|
+
local success, source = pcall(function() return scriptObj.Source end)
|
|
1280
|
+
if not success or not source then return end
|
|
1281
|
+
|
|
1282
|
+
local lines = string.split(source, "\n")
|
|
1283
|
+
for i, line in ipairs(lines) do
|
|
1284
|
+
-- Coba cari dengan plain text dulu, jika gagal coba pattern
|
|
1285
|
+
local found = string.find(line, query, 1, true)
|
|
1286
|
+
if not found then
|
|
1287
|
+
pcall(function() found = string.find(line, query) end)
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
if found then
|
|
1291
|
+
table.insert(matches, {
|
|
1292
|
+
script = scriptObj:GetFullName(),
|
|
1293
|
+
line = i,
|
|
1294
|
+
content = string.match(line, "^%s*(.-)%s*$") or line
|
|
1295
|
+
})
|
|
1296
|
+
matchCount = matchCount + 1
|
|
1297
|
+
if matchCount >= MAX_MATCHES then
|
|
1298
|
+
return true
|
|
1299
|
+
end
|
|
1300
|
+
end
|
|
1301
|
+
end
|
|
1302
|
+
return false
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
for _, service in ipairs(searchableServices) do
|
|
1306
|
+
pcall(function()
|
|
1307
|
+
local descendants = service:GetDescendants()
|
|
1308
|
+
for _, desc in ipairs(descendants) do
|
|
1309
|
+
if desc:IsA("LuaSourceContainer") then
|
|
1310
|
+
if searchInScript(desc) then
|
|
1311
|
+
break
|
|
1312
|
+
end
|
|
1313
|
+
end
|
|
1314
|
+
end
|
|
1315
|
+
end)
|
|
1316
|
+
if matchCount >= MAX_MATCHES then
|
|
1317
|
+
break
|
|
1318
|
+
end
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
return { status = "success", matches = matches, count = matchCount, limitReached = (matchCount >= MAX_MATCHES) }
|
|
1322
|
+
end
|
|
1323
|
+
]]></ProtectedString>
|
|
1324
|
+
<string name="ScriptGuid">{74D52488-8890-4D93-B5D6-37BBE9F0DF1E}</string>
|
|
1325
|
+
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1326
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1327
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1328
|
+
<string name="Name">script_grep</string>
|
|
1329
|
+
<int64 name="SourceAssetId">-1</int64>
|
|
1330
|
+
<SharedString name="Tags">yuZpQdnvvUBOTYh1jqZ2cA==</SharedString>
|
|
1331
|
+
</Properties>
|
|
1332
|
+
</Item>
|
|
1333
|
+
</Item>
|
|
1334
|
+
<SharedStrings>
|
|
1335
|
+
<SharedString md5="yuZpQdnvvUBOTYh1jqZ2cA=="></SharedString>
|
|
1336
|
+
</SharedStrings>
|
|
1337
|
+
</roblox>
|