@chrrxs/robloxstudio-mcp-inspector 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4483 -0
- package/package.json +50 -0
- package/studio-plugin/INSTALLATION.md +150 -0
- package/studio-plugin/MCPInspectorPlugin.rbxmx +9074 -0
- package/studio-plugin/MCPPlugin.rbxmx +9074 -0
- package/studio-plugin/default.project.json +19 -0
- package/studio-plugin/dev.project.json +23 -0
- package/studio-plugin/inspector-icon.png +0 -0
- package/studio-plugin/package-lock.json +706 -0
- package/studio-plugin/package.json +19 -0
- package/studio-plugin/plugin.json +10 -0
- package/studio-plugin/src/modules/ClientBroker.ts +221 -0
- package/studio-plugin/src/modules/Communication.ts +399 -0
- package/studio-plugin/src/modules/Recording.ts +28 -0
- package/studio-plugin/src/modules/State.ts +94 -0
- package/studio-plugin/src/modules/UI.ts +725 -0
- package/studio-plugin/src/modules/Utils.ts +318 -0
- package/studio-plugin/src/modules/handlers/AssetHandlers.ts +241 -0
- package/studio-plugin/src/modules/handlers/BuildHandlers.ts +481 -0
- package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +128 -0
- package/studio-plugin/src/modules/handlers/InputHandlers.ts +102 -0
- package/studio-plugin/src/modules/handlers/InstanceHandlers.ts +380 -0
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +391 -0
- package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +191 -0
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +827 -0
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +530 -0
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +277 -0
- package/studio-plugin/src/server/index.server.ts +63 -0
- package/studio-plugin/src/types/index.d.ts +44 -0
- package/studio-plugin/tsconfig.json +20 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { CollectionService } from "@rbxts/services";
|
|
2
|
+
import Utils from "../Utils";
|
|
3
|
+
import Recording from "../Recording";
|
|
4
|
+
|
|
5
|
+
const ChangeHistoryService = game.GetService("ChangeHistoryService");
|
|
6
|
+
const Selection = game.GetService("Selection");
|
|
7
|
+
|
|
8
|
+
const { getInstancePath, getInstanceByPath } = Utils;
|
|
9
|
+
const { beginRecording, finishRecording } = Recording;
|
|
10
|
+
|
|
11
|
+
function serializeValue(value: unknown): unknown {
|
|
12
|
+
const vType = typeOf(value);
|
|
13
|
+
if (vType === "Vector3") {
|
|
14
|
+
const v = value as Vector3;
|
|
15
|
+
return { X: v.X, Y: v.Y, Z: v.Z, _type: "Vector3" };
|
|
16
|
+
} else if (vType === "Color3") {
|
|
17
|
+
const v = value as Color3;
|
|
18
|
+
return { R: v.R, G: v.G, B: v.B, _type: "Color3" };
|
|
19
|
+
} else if (vType === "CFrame") {
|
|
20
|
+
const v = value as CFrame;
|
|
21
|
+
return { Position: { X: v.Position.X, Y: v.Position.Y, Z: v.Position.Z }, _type: "CFrame" };
|
|
22
|
+
} else if (vType === "UDim2") {
|
|
23
|
+
const v = value as UDim2;
|
|
24
|
+
return {
|
|
25
|
+
X: { Scale: v.X.Scale, Offset: v.X.Offset },
|
|
26
|
+
Y: { Scale: v.Y.Scale, Offset: v.Y.Offset },
|
|
27
|
+
_type: "UDim2",
|
|
28
|
+
};
|
|
29
|
+
} else if (vType === "BrickColor") {
|
|
30
|
+
const v = value as BrickColor;
|
|
31
|
+
return { Name: v.Name, _type: "BrickColor" };
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function deserializeValue(attributeValue: unknown, valueType?: string): unknown {
|
|
37
|
+
if (!typeIs(attributeValue, "table")) return attributeValue;
|
|
38
|
+
|
|
39
|
+
const tbl = attributeValue as Record<string, unknown>;
|
|
40
|
+
const t = (tbl._type as string) ?? valueType;
|
|
41
|
+
|
|
42
|
+
if (t === "Vector3") {
|
|
43
|
+
return new Vector3((tbl.X as number) ?? 0, (tbl.Y as number) ?? 0, (tbl.Z as number) ?? 0);
|
|
44
|
+
} else if (t === "Color3") {
|
|
45
|
+
return new Color3((tbl.R as number) ?? 0, (tbl.G as number) ?? 0, (tbl.B as number) ?? 0);
|
|
46
|
+
} else if (t === "UDim2") {
|
|
47
|
+
const x = tbl.X as Record<string, number> | undefined;
|
|
48
|
+
const y = tbl.Y as Record<string, number> | undefined;
|
|
49
|
+
return new UDim2(x?.Scale ?? 0, x?.Offset ?? 0, y?.Scale ?? 0, y?.Offset ?? 0);
|
|
50
|
+
} else if (t === "BrickColor") {
|
|
51
|
+
return new BrickColor(((tbl.Name as string) ?? "Medium stone grey") as unknown as number);
|
|
52
|
+
}
|
|
53
|
+
return attributeValue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setAttribute(requestData: Record<string, unknown>) {
|
|
57
|
+
const instancePath = requestData.instancePath as string;
|
|
58
|
+
const attributeName = requestData.attributeName as string;
|
|
59
|
+
const attributeValue = requestData.attributeValue;
|
|
60
|
+
const valueType = requestData.valueType as string | undefined;
|
|
61
|
+
|
|
62
|
+
if (!instancePath || !attributeName) {
|
|
63
|
+
return { error: "Instance path and attribute name are required" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const instance = getInstanceByPath(instancePath);
|
|
67
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
68
|
+
const recordingId = beginRecording(`Set attribute ${attributeName} on ${instance.Name}`);
|
|
69
|
+
|
|
70
|
+
const [success, result] = pcall(() => {
|
|
71
|
+
const value = deserializeValue(attributeValue, valueType);
|
|
72
|
+
instance.SetAttribute(attributeName, value as AttributeValue);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
success: true, instancePath, attributeName,
|
|
76
|
+
value: attributeValue, message: "Attribute set successfully",
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (success) {
|
|
81
|
+
finishRecording(recordingId, true);
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
finishRecording(recordingId, false);
|
|
85
|
+
return { error: `Failed to set attribute: ${result}` };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getAttributes(requestData: Record<string, unknown>) {
|
|
89
|
+
const instancePath = requestData.instancePath as string;
|
|
90
|
+
if (!instancePath) return { error: "Instance path is required" };
|
|
91
|
+
|
|
92
|
+
const instance = getInstanceByPath(instancePath);
|
|
93
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
94
|
+
|
|
95
|
+
const [success, result] = pcall(() => {
|
|
96
|
+
const attributes = instance.GetAttributes();
|
|
97
|
+
const serializedAttributes: Record<string, { value: unknown; type: string }> = {};
|
|
98
|
+
let count = 0;
|
|
99
|
+
|
|
100
|
+
for (const [name, value] of pairs(attributes)) {
|
|
101
|
+
serializedAttributes[name as string] = {
|
|
102
|
+
value: serializeValue(value),
|
|
103
|
+
type: typeOf(value),
|
|
104
|
+
};
|
|
105
|
+
count++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { instancePath, attributes: serializedAttributes, count };
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (success) return result;
|
|
112
|
+
return { error: `Failed to get attributes: ${result}` };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function deleteAttribute(requestData: Record<string, unknown>) {
|
|
116
|
+
const instancePath = requestData.instancePath as string;
|
|
117
|
+
const attributeName = requestData.attributeName as string;
|
|
118
|
+
|
|
119
|
+
if (!instancePath || !attributeName) {
|
|
120
|
+
return { error: "Instance path and attribute name are required" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const instance = getInstanceByPath(instancePath);
|
|
124
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
125
|
+
const recordingId = beginRecording(`Delete attribute ${attributeName} from ${instance.Name}`);
|
|
126
|
+
|
|
127
|
+
const [success, result] = pcall(() => {
|
|
128
|
+
const existed = instance.GetAttribute(attributeName) !== undefined;
|
|
129
|
+
instance.SetAttribute(attributeName, undefined);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
success: true, instancePath, attributeName, existed,
|
|
133
|
+
message: existed ? "Attribute deleted successfully" : "Attribute did not exist",
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (success) {
|
|
138
|
+
finishRecording(recordingId, true);
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
finishRecording(recordingId, false);
|
|
142
|
+
return { error: `Failed to delete attribute: ${result}` };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getTags(requestData: Record<string, unknown>) {
|
|
146
|
+
const instancePath = requestData.instancePath as string;
|
|
147
|
+
if (!instancePath) return { error: "Instance path is required" };
|
|
148
|
+
|
|
149
|
+
const instance = getInstanceByPath(instancePath);
|
|
150
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
151
|
+
|
|
152
|
+
const [success, result] = pcall(() => {
|
|
153
|
+
const tags = CollectionService.GetTags(instance);
|
|
154
|
+
return { instancePath, tags, count: tags.size() };
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (success) return result;
|
|
158
|
+
return { error: `Failed to get tags: ${result}` };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function addTag(requestData: Record<string, unknown>) {
|
|
162
|
+
const instancePath = requestData.instancePath as string;
|
|
163
|
+
const tagName = requestData.tagName as string;
|
|
164
|
+
|
|
165
|
+
if (!instancePath || !tagName) {
|
|
166
|
+
return { error: "Instance path and tag name are required" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const instance = getInstanceByPath(instancePath);
|
|
170
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
171
|
+
const recordingId = beginRecording(`Add tag ${tagName} to ${instance.Name}`);
|
|
172
|
+
|
|
173
|
+
const [success, result] = pcall(() => {
|
|
174
|
+
const alreadyHad = CollectionService.HasTag(instance, tagName);
|
|
175
|
+
CollectionService.AddTag(instance, tagName);
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
success: true, instancePath, tagName, alreadyHad,
|
|
179
|
+
message: alreadyHad ? "Instance already had this tag" : "Tag added successfully",
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (success) {
|
|
184
|
+
finishRecording(recordingId, true);
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
finishRecording(recordingId, false);
|
|
188
|
+
return { error: `Failed to add tag: ${result}` };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function removeTag(requestData: Record<string, unknown>) {
|
|
192
|
+
const instancePath = requestData.instancePath as string;
|
|
193
|
+
const tagName = requestData.tagName as string;
|
|
194
|
+
|
|
195
|
+
if (!instancePath || !tagName) {
|
|
196
|
+
return { error: "Instance path and tag name are required" };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const instance = getInstanceByPath(instancePath);
|
|
200
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
201
|
+
const recordingId = beginRecording(`Remove tag ${tagName} from ${instance.Name}`);
|
|
202
|
+
|
|
203
|
+
const [success, result] = pcall(() => {
|
|
204
|
+
const hadTag = CollectionService.HasTag(instance, tagName);
|
|
205
|
+
CollectionService.RemoveTag(instance, tagName);
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
success: true, instancePath, tagName, hadTag,
|
|
209
|
+
message: hadTag ? "Tag removed successfully" : "Instance did not have this tag",
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
if (success) {
|
|
214
|
+
finishRecording(recordingId, true);
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
finishRecording(recordingId, false);
|
|
218
|
+
return { error: `Failed to remove tag: ${result}` };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getTagged(requestData: Record<string, unknown>) {
|
|
222
|
+
const tagName = requestData.tagName as string;
|
|
223
|
+
if (!tagName) return { error: "Tag name is required" };
|
|
224
|
+
|
|
225
|
+
const [success, result] = pcall(() => {
|
|
226
|
+
const taggedInstances = CollectionService.GetTagged(tagName);
|
|
227
|
+
const instances = taggedInstances.map((instance) => ({
|
|
228
|
+
name: instance.Name,
|
|
229
|
+
className: instance.ClassName,
|
|
230
|
+
path: getInstancePath(instance),
|
|
231
|
+
}));
|
|
232
|
+
|
|
233
|
+
return { tagName, instances, count: instances.size() };
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (success) return result;
|
|
237
|
+
return { error: `Failed to get tagged instances: ${result}` };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getSelection(_requestData: Record<string, unknown>) {
|
|
241
|
+
const selection = Selection.Get();
|
|
242
|
+
|
|
243
|
+
if (selection.size() === 0) {
|
|
244
|
+
return { success: true, selection: [], count: 0, message: "No objects selected" };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const selectedObjects = selection.map((instance: Instance) => ({
|
|
248
|
+
name: instance.Name,
|
|
249
|
+
className: instance.ClassName,
|
|
250
|
+
path: getInstancePath(instance),
|
|
251
|
+
parent: instance.Parent ? getInstancePath(instance.Parent) : undefined,
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
selection: selectedObjects,
|
|
257
|
+
count: selection.size(),
|
|
258
|
+
message: `${selection.size()} object(s) selected`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function executeLuau(requestData: Record<string, unknown>) {
|
|
263
|
+
const code = requestData.code as string;
|
|
264
|
+
if (!code || code === "") return { error: "Code is required" };
|
|
265
|
+
|
|
266
|
+
const output: string[] = [];
|
|
267
|
+
const oldPrint = print;
|
|
268
|
+
const oldWarn = warn;
|
|
269
|
+
|
|
270
|
+
const env = getfenv(0) as unknown as Record<string, unknown>;
|
|
271
|
+
env["print"] = (...args: defined[]) => {
|
|
272
|
+
const parts: string[] = [];
|
|
273
|
+
for (const a of args) parts.push(tostring(a));
|
|
274
|
+
output.push(parts.join("\t"));
|
|
275
|
+
oldPrint(...(args as [defined, ...defined[]]));
|
|
276
|
+
};
|
|
277
|
+
env["warn"] = (...args: defined[]) => {
|
|
278
|
+
const parts: string[] = [];
|
|
279
|
+
for (const a of args) parts.push(tostring(a));
|
|
280
|
+
output.push(`[warn] ${parts.join("\t")}`);
|
|
281
|
+
oldWarn(...(args as [defined, ...defined[]]));
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const [success, result] = pcall(() => {
|
|
285
|
+
const [fn, compileError] = loadstring(code);
|
|
286
|
+
if (!fn) error(`Compile error: ${compileError}`);
|
|
287
|
+
return fn();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
env["print"] = oldPrint;
|
|
291
|
+
env["warn"] = oldWarn;
|
|
292
|
+
|
|
293
|
+
if (success) {
|
|
294
|
+
return {
|
|
295
|
+
success: true,
|
|
296
|
+
returnValue: result !== undefined ? tostring(result) : undefined,
|
|
297
|
+
output,
|
|
298
|
+
message: "Code executed successfully",
|
|
299
|
+
};
|
|
300
|
+
} else {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: tostring(result),
|
|
304
|
+
output,
|
|
305
|
+
message: "Code execution failed",
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function undo(_requestData: Record<string, unknown>) {
|
|
311
|
+
const [success, result] = pcall(() => {
|
|
312
|
+
ChangeHistoryService.Undo();
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
message: "Undo executed successfully",
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (success) return result;
|
|
320
|
+
return { error: `Failed to undo: ${result}` };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function redo(_requestData: Record<string, unknown>) {
|
|
324
|
+
const [success, result] = pcall(() => {
|
|
325
|
+
ChangeHistoryService.Redo();
|
|
326
|
+
return {
|
|
327
|
+
success: true,
|
|
328
|
+
message: "Redo executed successfully",
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (success) return result;
|
|
333
|
+
return { error: `Failed to redo: ${result}` };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function bulkSetAttributes(requestData: Record<string, unknown>) {
|
|
337
|
+
const instancePath = requestData.instancePath as string;
|
|
338
|
+
const attributes = requestData.attributes as Record<string, unknown>;
|
|
339
|
+
|
|
340
|
+
if (!instancePath || !attributes) {
|
|
341
|
+
return { error: "Instance path and attributes are required" };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const instance = getInstanceByPath(instancePath);
|
|
345
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
346
|
+
|
|
347
|
+
const recordingId = beginRecording(`Bulk set attributes on ${instance.Name}`);
|
|
348
|
+
|
|
349
|
+
const results: Record<string, unknown>[] = [];
|
|
350
|
+
let successCount = 0;
|
|
351
|
+
let failureCount = 0;
|
|
352
|
+
|
|
353
|
+
for (const [name, rawValue] of pairs(attributes)) {
|
|
354
|
+
const attrName = name as string;
|
|
355
|
+
const [ok, err] = pcall(() => {
|
|
356
|
+
const value = deserializeValue(rawValue);
|
|
357
|
+
instance.SetAttribute(attrName, value as AttributeValue);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (ok) {
|
|
361
|
+
successCount++;
|
|
362
|
+
results.push({ attributeName: attrName, success: true });
|
|
363
|
+
} else {
|
|
364
|
+
failureCount++;
|
|
365
|
+
results.push({ attributeName: attrName, success: false, error: tostring(err) });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
finishRecording(recordingId, successCount > 0);
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
instancePath,
|
|
373
|
+
results,
|
|
374
|
+
summary: { total: successCount + failureCount, succeeded: successCount, failed: failureCount },
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export = {
|
|
379
|
+
setAttribute,
|
|
380
|
+
getAttributes,
|
|
381
|
+
deleteAttribute,
|
|
382
|
+
getTags,
|
|
383
|
+
addTag,
|
|
384
|
+
removeTag,
|
|
385
|
+
getTagged,
|
|
386
|
+
getSelection,
|
|
387
|
+
executeLuau,
|
|
388
|
+
undo,
|
|
389
|
+
redo,
|
|
390
|
+
bulkSetAttributes,
|
|
391
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import Utils from "../Utils";
|
|
2
|
+
import Recording from "../Recording";
|
|
3
|
+
|
|
4
|
+
const { getInstanceByPath, convertPropertyValue } = Utils;
|
|
5
|
+
const { beginRecording, finishRecording } = Recording;
|
|
6
|
+
|
|
7
|
+
function setProperty(requestData: Record<string, unknown>) {
|
|
8
|
+
const instancePath = requestData.instancePath as string;
|
|
9
|
+
const propertyName = requestData.propertyName as string;
|
|
10
|
+
const propertyValue = requestData.propertyValue;
|
|
11
|
+
|
|
12
|
+
if (!instancePath || !propertyName) {
|
|
13
|
+
return { error: "Instance path and property name are required" };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const instance = getInstanceByPath(instancePath);
|
|
17
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
18
|
+
const recordingId = beginRecording(`Set ${propertyName} property`);
|
|
19
|
+
|
|
20
|
+
const inst = instance as unknown as Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
const [success, result] = pcall(() => {
|
|
23
|
+
if (propertyName === "Parent" || propertyName === "PrimaryPart") {
|
|
24
|
+
if (typeIs(propertyValue, "string")) {
|
|
25
|
+
const refInstance = getInstanceByPath(propertyValue);
|
|
26
|
+
if (refInstance) {
|
|
27
|
+
inst[propertyName] = refInstance;
|
|
28
|
+
} else {
|
|
29
|
+
return { error: `${propertyName} instance not found: ${propertyValue}` };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} else if (propertyName === "Name") {
|
|
33
|
+
instance.Name = tostring(propertyValue);
|
|
34
|
+
} else if (propertyName === "Source" && instance.IsA("LuaSourceContainer")) {
|
|
35
|
+
(instance as unknown as { Source: string }).Source = tostring(propertyValue);
|
|
36
|
+
} else {
|
|
37
|
+
const convertedValue = convertPropertyValue(instance, propertyName, propertyValue);
|
|
38
|
+
if (convertedValue !== undefined) {
|
|
39
|
+
inst[propertyName] = convertedValue;
|
|
40
|
+
} else {
|
|
41
|
+
inst[propertyName] = propertyValue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (success) {
|
|
49
|
+
finishRecording(recordingId, true);
|
|
50
|
+
return {
|
|
51
|
+
success: true,
|
|
52
|
+
instancePath,
|
|
53
|
+
propertyName,
|
|
54
|
+
propertyValue,
|
|
55
|
+
message: "Property set successfully",
|
|
56
|
+
};
|
|
57
|
+
} else {
|
|
58
|
+
finishRecording(recordingId, false);
|
|
59
|
+
return { error: `Failed to set property: ${result}`, instancePath, propertyName };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function massSetProperty(requestData: Record<string, unknown>) {
|
|
64
|
+
const paths = requestData.paths as string[];
|
|
65
|
+
const propertyName = requestData.propertyName as string;
|
|
66
|
+
const propertyValue = requestData.propertyValue;
|
|
67
|
+
|
|
68
|
+
if (!paths || !typeIs(paths, "table") || (paths as defined[]).size() === 0 || !propertyName) {
|
|
69
|
+
return { error: "Paths array and property name are required" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const results: Record<string, unknown>[] = [];
|
|
73
|
+
let successCount = 0;
|
|
74
|
+
let failureCount = 0;
|
|
75
|
+
const recordingId = beginRecording(`Mass set ${propertyName} property`);
|
|
76
|
+
|
|
77
|
+
for (const path of paths) {
|
|
78
|
+
const instance = getInstanceByPath(path);
|
|
79
|
+
if (instance) {
|
|
80
|
+
const [success, err] = pcall(() => {
|
|
81
|
+
const converted = convertPropertyValue(instance, propertyName, propertyValue);
|
|
82
|
+
(instance as unknown as Record<string, unknown>)[propertyName] =
|
|
83
|
+
converted !== undefined ? converted : propertyValue;
|
|
84
|
+
});
|
|
85
|
+
if (success) {
|
|
86
|
+
successCount++;
|
|
87
|
+
results.push({ path, success: true, propertyName, propertyValue });
|
|
88
|
+
} else {
|
|
89
|
+
failureCount++;
|
|
90
|
+
results.push({ path, success: false, error: tostring(err) });
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
failureCount++;
|
|
94
|
+
results.push({ path, success: false, error: "Instance not found" });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
finishRecording(recordingId, successCount > 0);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
results,
|
|
102
|
+
summary: { total: paths.size(), succeeded: successCount, failed: failureCount },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function massGetProperty(requestData: Record<string, unknown>) {
|
|
107
|
+
const paths = requestData.paths as string[];
|
|
108
|
+
const propertyName = requestData.propertyName as string;
|
|
109
|
+
|
|
110
|
+
if (!paths || !typeIs(paths, "table") || (paths as defined[]).size() === 0 || !propertyName) {
|
|
111
|
+
return { error: "Paths array and property name are required" };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const results: Record<string, unknown>[] = [];
|
|
115
|
+
|
|
116
|
+
for (const path of paths) {
|
|
117
|
+
const instance = getInstanceByPath(path);
|
|
118
|
+
if (instance) {
|
|
119
|
+
const [success, value] = pcall(() => (instance as unknown as Record<string, unknown>)[propertyName]);
|
|
120
|
+
if (success) {
|
|
121
|
+
results.push({ path, success: true, propertyName, propertyValue: value });
|
|
122
|
+
} else {
|
|
123
|
+
results.push({ path, success: false, error: tostring(value) });
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
results.push({ path, success: false, error: "Instance not found" });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { results, propertyName };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function setProperties(requestData: Record<string, unknown>) {
|
|
134
|
+
const instancePath = requestData.instancePath as string;
|
|
135
|
+
const properties = requestData.properties as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
if (!instancePath || !properties || !typeIs(properties, "table")) {
|
|
138
|
+
return { error: "Instance path and properties object are required" };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const instance = getInstanceByPath(instancePath);
|
|
142
|
+
if (!instance) return { error: `Instance not found: ${instancePath}` };
|
|
143
|
+
|
|
144
|
+
const recordingId = beginRecording("Set multiple properties");
|
|
145
|
+
const inst = instance as unknown as Record<string, unknown>;
|
|
146
|
+
const results: Record<string, unknown>[] = [];
|
|
147
|
+
let successCount = 0;
|
|
148
|
+
let failureCount = 0;
|
|
149
|
+
|
|
150
|
+
for (const [propName, propValue] of pairs(properties)) {
|
|
151
|
+
const [success, err] = pcall(() => {
|
|
152
|
+
if (propName === "Parent" || propName === "PrimaryPart") {
|
|
153
|
+
if (typeIs(propValue, "string")) {
|
|
154
|
+
const refInstance = getInstanceByPath(propValue as string);
|
|
155
|
+
if (!refInstance) error(`${propName} reference not found: ${propValue}`);
|
|
156
|
+
inst[propName as string] = refInstance;
|
|
157
|
+
}
|
|
158
|
+
} else if (propName === "Name") {
|
|
159
|
+
instance.Name = tostring(propValue);
|
|
160
|
+
} else if (propName === "Source" && instance.IsA("LuaSourceContainer")) {
|
|
161
|
+
(instance as unknown as { Source: string }).Source = tostring(propValue);
|
|
162
|
+
} else {
|
|
163
|
+
const converted = convertPropertyValue(instance, propName as string, propValue);
|
|
164
|
+
inst[propName as string] = converted !== undefined ? converted : propValue;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (success) {
|
|
169
|
+
successCount++;
|
|
170
|
+
results.push({ property: propName, success: true });
|
|
171
|
+
} else {
|
|
172
|
+
failureCount++;
|
|
173
|
+
results.push({ property: propName, success: false, error: tostring(err) });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
finishRecording(recordingId, successCount > 0);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
instancePath,
|
|
181
|
+
summary: { total: successCount + failureCount, succeeded: successCount, failed: failureCount },
|
|
182
|
+
results,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export = {
|
|
187
|
+
setProperty,
|
|
188
|
+
massSetProperty,
|
|
189
|
+
massGetProperty,
|
|
190
|
+
setProperties,
|
|
191
|
+
};
|