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