@chrrxs/robloxstudio-mcp 2.23.0 → 3.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.
@@ -778,11 +778,9 @@ local UI = TS.import(script, script.Parent, "UI")
778
778
  local cleanupLegacyEditBridges = TS.import(script, script.Parent, "EvalBridges").cleanupLegacyEditBridges
779
779
  local QueryHandlers = TS.import(script, script.Parent, "handlers", "QueryHandlers")
780
780
  local PropertyHandlers = TS.import(script, script.Parent, "handlers", "PropertyHandlers")
781
- local InstanceHandlers = TS.import(script, script.Parent, "handlers", "InstanceHandlers")
782
781
  local ScriptHandlers = TS.import(script, script.Parent, "handlers", "ScriptHandlers")
783
782
  local MetadataHandlers = TS.import(script, script.Parent, "handlers", "MetadataHandlers")
784
783
  local TestHandlers = TS.import(script, script.Parent, "handlers", "TestHandlers")
785
- local BuildHandlers = TS.import(script, script.Parent, "handlers", "BuildHandlers")
786
784
  local AssetHandlers = TS.import(script, script.Parent, "handlers", "AssetHandlers")
787
785
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
788
786
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
@@ -873,45 +871,22 @@ local routeMap = {
873
871
  ["/api/file-tree"] = QueryHandlers.getFileTree,
874
872
  ["/api/search-files"] = QueryHandlers.searchFiles,
875
873
  ["/api/place-info"] = QueryHandlers.getPlaceInfo,
876
- ["/api/services"] = QueryHandlers.getServices,
877
874
  ["/api/search-objects"] = QueryHandlers.searchObjects,
878
875
  ["/api/instance-properties"] = QueryHandlers.getInstanceProperties,
879
- ["/api/instance-children"] = QueryHandlers.getInstanceChildren,
880
876
  ["/api/search-by-property"] = QueryHandlers.searchByProperty,
881
877
  ["/api/class-info"] = QueryHandlers.getClassInfo,
882
878
  ["/api/project-structure"] = QueryHandlers.getProjectStructure,
883
879
  ["/api/grep-scripts"] = QueryHandlers.grepScripts,
884
- ["/api/get-descendants"] = QueryHandlers.getDescendants,
885
- ["/api/compare-instances"] = QueryHandlers.compareInstances,
886
- ["/api/set-property"] = PropertyHandlers.setProperty,
887
880
  ["/api/set-properties"] = PropertyHandlers.setProperties,
888
- ["/api/mass-set-property"] = PropertyHandlers.massSetProperty,
889
- ["/api/mass-get-property"] = PropertyHandlers.massGetProperty,
890
- ["/api/create-object"] = InstanceHandlers.createObject,
891
- ["/api/mass-create-objects"] = InstanceHandlers.massCreateObjects,
892
- ["/api/mass-create-objects-with-properties"] = InstanceHandlers.massCreateObjects,
893
- ["/api/delete-object"] = InstanceHandlers.deleteObject,
894
- ["/api/smart-duplicate"] = InstanceHandlers.smartDuplicate,
895
- ["/api/mass-duplicate"] = InstanceHandlers.massDuplicate,
896
- ["/api/clone-object"] = InstanceHandlers.cloneObject,
897
881
  ["/api/get-script-source"] = ScriptHandlers.getScriptSource,
898
882
  ["/api/set-script-source"] = ScriptHandlers.setScriptSource,
899
883
  ["/api/edit-script-lines"] = ScriptHandlers.editScriptLines,
900
884
  ["/api/insert-script-lines"] = ScriptHandlers.insertScriptLines,
901
885
  ["/api/delete-script-lines"] = ScriptHandlers.deleteScriptLines,
902
- ["/api/set-attribute"] = MetadataHandlers.setAttribute,
903
886
  ["/api/get-attributes"] = MetadataHandlers.getAttributes,
904
- ["/api/delete-attribute"] = MetadataHandlers.deleteAttribute,
905
- ["/api/get-tags"] = MetadataHandlers.getTags,
906
- ["/api/add-tag"] = MetadataHandlers.addTag,
907
- ["/api/remove-tag"] = MetadataHandlers.removeTag,
908
- ["/api/get-tagged"] = MetadataHandlers.getTagged,
909
887
  ["/api/get-selection"] = MetadataHandlers.getSelection,
910
888
  ["/api/execute-luau"] = MetadataHandlers.executeLuau,
911
889
  ["/api/eval-runtime"] = EvalRuntimeHandlers.evalRuntime,
912
- ["/api/undo"] = MetadataHandlers.undo,
913
- ["/api/redo"] = MetadataHandlers.redo,
914
- ["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
915
890
  ["/api/start-playtest"] = TestHandlers.startPlaytest,
916
891
  ["/api/stop-playtest"] = TestHandlers.stopPlaytest,
917
892
  ["/api/multiplayer-test-start"] = TestHandlers.multiplayerTestStart,
@@ -919,10 +894,6 @@ local routeMap = {
919
894
  ["/api/multiplayer-test-add-players"] = TestHandlers.multiplayerTestAddPlayers,
920
895
  ["/api/multiplayer-test-leave-client"] = TestHandlers.multiplayerTestLeaveClient,
921
896
  ["/api/multiplayer-test-end"] = TestHandlers.multiplayerTestEnd,
922
- ["/api/export-build"] = BuildHandlers.exportBuild,
923
- ["/api/import-build"] = BuildHandlers.importBuild,
924
- ["/api/import-scene"] = BuildHandlers.importScene,
925
- ["/api/search-materials"] = BuildHandlers.searchMaterials,
926
897
  ["/api/insert-asset"] = AssetHandlers.insertAsset,
927
898
  ["/api/preview-asset"] = AssetHandlers.previewAsset,
928
899
  ["/api/capture-screenshot"] = CaptureHandlers.captureScreenshot,
@@ -1550,9 +1521,9 @@ local function computeBridgeStamp()
1550
1521
  for i = 1, #combined do
1551
1522
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1552
1523
  end
1553
- -- "2.23.0" is replaced with the package version at package time
1524
+ -- "3.0.0" is replaced with the package version at package time
1554
1525
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1555
- return `{tostring(h)}-2.23.0`
1526
+ return `{tostring(h)}-3.0.0`
1556
1527
  end
1557
1528
  local BRIDGE_STAMP = computeBridgeStamp()
1558
1529
  local function setSource(scriptInst, source)
@@ -2669,571 +2640,6 @@ return {
2669
2640
  </Properties>
2670
2641
  </Item>
2671
2642
  <Item class="ModuleScript" referent="9">
2672
- <Properties>
2673
- <string name="Name">BuildHandlers</string>
2674
- <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
2675
- local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
2676
- local Utils = TS.import(script, script.Parent.Parent, "Utils")
2677
- local Recording = TS.import(script, script.Parent.Parent, "Recording")
2678
- local MaterialService = game:GetService("MaterialService")
2679
- local _binding = Utils
2680
- local getInstancePath = _binding.getInstancePath
2681
- local getInstanceByPath = _binding.getInstanceByPath
2682
- local _binding_1 = Recording
2683
- local beginRecording = _binding_1.beginRecording
2684
- local finishRecording = _binding_1.finishRecording
2685
- local MATERIAL_BY_NAME = {}
2686
- for _, enumItem in Enum.Material:GetEnumItems() do
2687
- local _name = enumItem.Name
2688
- MATERIAL_BY_NAME[_name] = enumItem
2689
- end
2690
- -- Shape class mapping
2691
- local SHAPE_CLASSES = {
2692
- Block = "Part",
2693
- Wedge = "WedgePart",
2694
- Cylinder = "Part",
2695
- Ball = "Part",
2696
- CornerWedge = "CornerWedgePart",
2697
- }
2698
- local PALETTE_KEYS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
2699
- local function roundTo(n, decimals)
2700
- local mult = 10 ^ decimals
2701
- return math.round(n * mult) / mult
2702
- end
2703
- local function encodePaletteKey(index)
2704
- local base = #PALETTE_KEYS
2705
- local value = math.floor(index) + 1
2706
- local encoded = ""
2707
- while value > 0 do
2708
- value -= 1
2709
- local digit = value % base
2710
- local _arg0 = digit + 1
2711
- local _arg1 = digit + 1
2712
- encoded = string.sub(PALETTE_KEYS, _arg0, _arg1) .. encoded
2713
- value = math.floor(value / base)
2714
- end
2715
- return encoded
2716
- end
2717
- local function getVariantName(part)
2718
- local variantName = part.MaterialVariant
2719
- if variantName == "" then
2720
- local ok, variantAttr = pcall(function()
2721
- return part:GetAttribute("MaterialVariant")
2722
- end)
2723
- if ok and type(variantAttr) == "string" then
2724
- variantName = variantAttr
2725
- end
2726
- end
2727
- return variantName
2728
- end
2729
- local function exportBuild(requestData)
2730
- local instancePath = requestData.instancePath
2731
- local outputId = requestData.outputId
2732
- local _condition = (requestData.style)
2733
- if _condition == nil then
2734
- _condition = "misc"
2735
- end
2736
- local style = _condition
2737
- if not (instancePath ~= "" and instancePath) then
2738
- return {
2739
- error = "Instance path is required",
2740
- }
2741
- end
2742
- local instance = getInstanceByPath(instancePath)
2743
- if not instance then
2744
- return {
2745
- error = `Instance not found: {instancePath}`,
2746
- }
2747
- end
2748
- if not instance:IsA("Model") and not instance:IsA("Folder") then
2749
- return {
2750
- error = "Instance must be a Model or Folder",
2751
- }
2752
- end
2753
- local success, result = pcall(function()
2754
- local descendants = instance:GetDescendants()
2755
- local baseParts = {}
2756
- for _, desc in descendants do
2757
- if desc:IsA("BasePart") then
2758
- table.insert(baseParts, desc)
2759
- end
2760
- end
2761
- if #baseParts == 0 then
2762
- return {
2763
- error = "No BaseParts found in instance",
2764
- }
2765
- end
2766
- -- Compute bounding box center
2767
- local minX = math.huge
2768
- local minY = math.huge
2769
- local minZ = math.huge
2770
- local maxX = -math.huge
2771
- local maxY = -math.huge
2772
- local maxZ = -math.huge
2773
- for _, part in baseParts do
2774
- local pos = part.Position
2775
- local sz = part.Size
2776
- local halfX = sz.X / 2
2777
- local halfY = sz.Y / 2
2778
- local halfZ = sz.Z / 2
2779
- minX = math.min(minX, pos.X - halfX)
2780
- minY = math.min(minY, pos.Y - halfY)
2781
- minZ = math.min(minZ, pos.Z - halfZ)
2782
- maxX = math.max(maxX, pos.X + halfX)
2783
- maxY = math.max(maxY, pos.Y + halfY)
2784
- maxZ = math.max(maxZ, pos.Z + halfZ)
2785
- end
2786
- local centerX = (minX + maxX) / 2
2787
- local centerY = minY
2788
- local centerZ = (minZ + maxZ) / 2
2789
- local boundsX = roundTo(maxX - minX, 1)
2790
- local boundsY = roundTo(maxY - minY, 1)
2791
- local boundsZ = roundTo(maxZ - minZ, 1)
2792
- -- Build palette from unique (BrickColor, Material, MaterialVariant?) combos
2793
- local paletteMap = {}
2794
- local palette = {}
2795
- local keyIndex = 0
2796
- for _, part in baseParts do
2797
- local colorName = part.BrickColor.Name
2798
- local materialName = part.Material.Name
2799
- local variantName = getVariantName(part)
2800
- local combo = if variantName ~= "" then `{colorName}|{materialName}|{variantName}` else `{colorName}|{materialName}`
2801
- if not (paletteMap[combo] ~= nil) then
2802
- local key = encodePaletteKey(keyIndex)
2803
- keyIndex += 1
2804
- paletteMap[combo] = key
2805
- if variantName ~= "" then
2806
- palette[key] = { colorName, materialName, variantName }
2807
- else
2808
- palette[key] = { colorName, materialName }
2809
- end
2810
- end
2811
- end
2812
- -- Build compact part arrays
2813
- local parts = {}
2814
- for _, part in baseParts do
2815
- local pos = part.Position
2816
- local orient = part.Orientation
2817
- local sz = part.Size
2818
- local colorName = part.BrickColor.Name
2819
- local materialName = part.Material.Name
2820
- local variantName = getVariantName(part)
2821
- local combo = if variantName ~= "" then `{colorName}|{materialName}|{variantName}` else `{colorName}|{materialName}`
2822
- local _condition_1 = paletteMap[combo]
2823
- if _condition_1 == nil then
2824
- _condition_1 = "a"
2825
- end
2826
- local paletteKey = _condition_1
2827
- -- Relative position to center
2828
- local relX = roundTo(pos.X - centerX, 1)
2829
- local relY = roundTo(pos.Y - centerY, 1)
2830
- local relZ = roundTo(pos.Z - centerZ, 1)
2831
- local sizeX = roundTo(sz.X, 1)
2832
- local sizeY = roundTo(sz.Y, 1)
2833
- local sizeZ = roundTo(sz.Z, 1)
2834
- local rotX = roundTo(orient.X, 1)
2835
- local rotY = roundTo(orient.Y, 1)
2836
- local rotZ = roundTo(orient.Z, 1)
2837
- -- Determine shape
2838
- local shape = "Block"
2839
- if part:IsA("WedgePart") then
2840
- shape = "Wedge"
2841
- elseif part:IsA("CornerWedgePart") then
2842
- shape = "CornerWedge"
2843
- elseif part:IsA("Part") then
2844
- local p = part
2845
- if p.Shape == Enum.PartType.Cylinder then
2846
- shape = "Cylinder"
2847
- elseif p.Shape == Enum.PartType.Ball then
2848
- shape = "Ball"
2849
- end
2850
- end
2851
- -- Build part array with optional trailing fields
2852
- local hasTransparency = part.Transparency > 0
2853
- local hasShape = shape ~= "Block"
2854
- local partArr
2855
- if hasTransparency then
2856
- partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey, if hasShape then shape else "Block", roundTo(part.Transparency, 2) }
2857
- elseif hasShape then
2858
- partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey, shape }
2859
- else
2860
- partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey }
2861
- end
2862
- local _partArr = partArr
2863
- table.insert(parts, _partArr)
2864
- end
2865
- local _condition_1 = outputId
2866
- if _condition_1 == nil then
2867
- _condition_1 = `{style}/{(string.gsub(string.lower(instance.Name), " ", "_"))}`
2868
- end
2869
- local buildId = _condition_1
2870
- return {
2871
- success = true,
2872
- buildData = {
2873
- id = buildId,
2874
- style = style,
2875
- bounds = { boundsX, boundsY, boundsZ },
2876
- palette = palette,
2877
- parts = parts,
2878
- },
2879
- }
2880
- end)
2881
- if success and result then
2882
- return result
2883
- else
2884
- return {
2885
- error = `Failed to export build: {result}`,
2886
- }
2887
- end
2888
- end
2889
- local function importBuild(requestData)
2890
- local buildData = requestData.buildData
2891
- local targetPath = requestData.targetPath
2892
- local positionOffset = (requestData.position) or { 0, 0, 0 }
2893
- if not buildData or not (targetPath ~= "" and targetPath) then
2894
- return {
2895
- error = "buildData and targetPath are required",
2896
- }
2897
- end
2898
- local parentInstance = getInstanceByPath(targetPath)
2899
- if not parentInstance then
2900
- return {
2901
- error = `Target not found: {targetPath}`,
2902
- }
2903
- end
2904
- local recordingId = beginRecording("Import build")
2905
- local success, result = pcall(function()
2906
- local palette = buildData.palette
2907
- local parts = buildData.parts
2908
- local _condition = (buildData.id)
2909
- if _condition == nil then
2910
- _condition = "imported_build"
2911
- end
2912
- local buildId = _condition
2913
- -- Create model container
2914
- local model = Instance.new("Model")
2915
- local _condition_1 = (string.match(buildId, "[^/]+$"))
2916
- if _condition_1 == nil then
2917
- _condition_1 = buildId
2918
- end
2919
- model.Name = _condition_1
2920
- local partCount = 0
2921
- for _, partArr in parts do
2922
- local _exp = (partArr[1])
2923
- local _condition_2 = positionOffset[1]
2924
- if _condition_2 == nil then
2925
- _condition_2 = 0
2926
- end
2927
- local posX = _exp + _condition_2
2928
- local _exp_1 = (partArr[2])
2929
- local _condition_3 = positionOffset[2]
2930
- if _condition_3 == nil then
2931
- _condition_3 = 0
2932
- end
2933
- local posY = _exp_1 + _condition_3
2934
- local _exp_2 = (partArr[3])
2935
- local _condition_4 = positionOffset[3]
2936
- if _condition_4 == nil then
2937
- _condition_4 = 0
2938
- end
2939
- local posZ = _exp_2 + _condition_4
2940
- local sizeX = partArr[4]
2941
- local sizeY = partArr[5]
2942
- local sizeZ = partArr[6]
2943
- local rotX = partArr[7]
2944
- local rotY = partArr[8]
2945
- local rotZ = partArr[9]
2946
- local paletteKey = partArr[10]
2947
- local _condition_5 = (partArr[11])
2948
- if _condition_5 == nil then
2949
- _condition_5 = "Block"
2950
- end
2951
- local shape = _condition_5
2952
- local _condition_6 = (partArr[12])
2953
- if _condition_6 == nil then
2954
- _condition_6 = 0
2955
- end
2956
- local transparency = _condition_6
2957
- -- Determine class from shape
2958
- local _condition_7 = SHAPE_CLASSES[shape]
2959
- if _condition_7 == nil then
2960
- _condition_7 = "Part"
2961
- end
2962
- local className = _condition_7
2963
- local part = Instance.new(className)
2964
- -- Set shape for Part instances with non-Block shapes
2965
- if className == "Part" and shape ~= "Block" then
2966
- if shape == "Cylinder" then
2967
- part.Shape = Enum.PartType.Cylinder
2968
- elseif shape == "Ball" then
2969
- part.Shape = Enum.PartType.Ball
2970
- end
2971
- end
2972
- part.Size = Vector3.new(sizeX, sizeY, sizeZ)
2973
- part.Position = Vector3.new(posX, posY, posZ)
2974
- part.Orientation = Vector3.new(rotX, rotY, rotZ)
2975
- part.Anchored = true
2976
- if transparency > 0 then
2977
- part.Transparency = transparency
2978
- end
2979
- -- Apply palette
2980
- local paletteEntry = palette[paletteKey]
2981
- if paletteEntry then
2982
- local _binding_2 = paletteEntry
2983
- local colorName = _binding_2[1]
2984
- local materialName = _binding_2[2]
2985
- local variantName = _binding_2[3]
2986
- pcall(function()
2987
- part.BrickColor = BrickColor.new(colorName)
2988
- end)
2989
- pcall(function()
2990
- local mat = MATERIAL_BY_NAME[materialName]
2991
- if mat ~= nil then
2992
- part.Material = mat
2993
- end
2994
- end)
2995
- -- Apply MaterialVariant if specified
2996
- if variantName ~= nil and variantName ~= "" then
2997
- pcall(function()
2998
- part.MaterialVariant = variantName
2999
- end)
3000
- end
3001
- end
3002
- part.Parent = model
3003
- partCount += 1
3004
- end
3005
- model.Parent = parentInstance
3006
- return {
3007
- success = true,
3008
- partCount = partCount,
3009
- modelPath = getInstancePath(model),
3010
- }
3011
- end)
3012
- if success and result then
3013
- finishRecording(recordingId, true)
3014
- return result
3015
- else
3016
- finishRecording(recordingId, false)
3017
- return {
3018
- error = `Failed to import build: {result}`,
3019
- }
3020
- end
3021
- end
3022
- local function importScene(requestData)
3023
- local expandedBuilds = requestData.expandedBuilds
3024
- local _condition = (requestData.targetPath)
3025
- if _condition == nil then
3026
- _condition = "game.Workspace"
3027
- end
3028
- local targetPath = _condition
3029
- if not expandedBuilds or not (type(expandedBuilds) == "table") or #expandedBuilds == 0 then
3030
- return {
3031
- error = "expandedBuilds array is required",
3032
- }
3033
- end
3034
- local parentInstance = getInstanceByPath(targetPath)
3035
- if not parentInstance then
3036
- return {
3037
- error = `Target not found: {targetPath}`,
3038
- }
3039
- end
3040
- local recordingId = beginRecording("Import scene")
3041
- local success, result = pcall(function()
3042
- local modelCount = 0
3043
- local totalParts = 0
3044
- local models = {}
3045
- for _, entry in expandedBuilds do
3046
- local buildData = entry.buildData
3047
- local position = (entry.position) or { 0, 0, 0 }
3048
- local rotation = (entry.rotation) or { 0, 0, 0 }
3049
- local _condition_1 = (entry.name)
3050
- if _condition_1 == nil then
3051
- _condition_1 = "SceneModel"
3052
- end
3053
- local name = _condition_1
3054
- local palette = buildData.palette
3055
- local parts = buildData.parts
3056
- local model = Instance.new("Model")
3057
- model.Name = name
3058
- local _condition_2 = rotation[1]
3059
- if _condition_2 == nil then
3060
- _condition_2 = 0
3061
- end
3062
- local _exp = math.rad(_condition_2)
3063
- local _condition_3 = rotation[2]
3064
- if _condition_3 == nil then
3065
- _condition_3 = 0
3066
- end
3067
- local _exp_1 = math.rad(_condition_3)
3068
- local _condition_4 = rotation[3]
3069
- if _condition_4 == nil then
3070
- _condition_4 = 0
3071
- end
3072
- local rotCF = CFrame.Angles(_exp, _exp_1, math.rad(_condition_4))
3073
- local _condition_5 = position[1]
3074
- if _condition_5 == nil then
3075
- _condition_5 = 0
3076
- end
3077
- local _condition_6 = position[2]
3078
- if _condition_6 == nil then
3079
- _condition_6 = 0
3080
- end
3081
- local _condition_7 = position[3]
3082
- if _condition_7 == nil then
3083
- _condition_7 = 0
3084
- end
3085
- local originCF = CFrame.new(_condition_5, _condition_6, _condition_7) * rotCF
3086
- local partCount = 0
3087
- for _1, partArr in parts do
3088
- local localX = partArr[1]
3089
- local localY = partArr[2]
3090
- local localZ = partArr[3]
3091
- local sizeX = partArr[4]
3092
- local sizeY = partArr[5]
3093
- local sizeZ = partArr[6]
3094
- local rotX = partArr[7]
3095
- local rotY = partArr[8]
3096
- local rotZ = partArr[9]
3097
- local paletteKey = partArr[10]
3098
- local _condition_8 = (partArr[11])
3099
- if _condition_8 == nil then
3100
- _condition_8 = "Block"
3101
- end
3102
- local shape = _condition_8
3103
- local _condition_9 = (partArr[12])
3104
- if _condition_9 == nil then
3105
- _condition_9 = 0
3106
- end
3107
- local transparency = _condition_9
3108
- local _condition_10 = SHAPE_CLASSES[shape]
3109
- if _condition_10 == nil then
3110
- _condition_10 = "Part"
3111
- end
3112
- local className = _condition_10
3113
- local part = Instance.new(className)
3114
- if className == "Part" and shape ~= "Block" then
3115
- if shape == "Cylinder" then
3116
- part.Shape = Enum.PartType.Cylinder
3117
- elseif shape == "Ball" then
3118
- part.Shape = Enum.PartType.Ball
3119
- end
3120
- end
3121
- part.Size = Vector3.new(sizeX, sizeY, sizeZ)
3122
- -- Apply local rotation then world transform
3123
- local localRotCF = CFrame.Angles(math.rad(rotX), math.rad(rotY), math.rad(rotZ))
3124
- local localPosCF = CFrame.new(localX, localY, localZ) * localRotCF
3125
- local worldCF = originCF * localPosCF
3126
- part.CFrame = worldCF
3127
- part.Anchored = true
3128
- if transparency > 0 then
3129
- part.Transparency = transparency
3130
- end
3131
- local paletteEntry = palette[paletteKey]
3132
- if paletteEntry then
3133
- local _binding_2 = paletteEntry
3134
- local colorName = _binding_2[1]
3135
- local materialName = _binding_2[2]
3136
- local variantName = _binding_2[3]
3137
- pcall(function()
3138
- part.BrickColor = BrickColor.new(colorName)
3139
- end)
3140
- pcall(function()
3141
- local mat = MATERIAL_BY_NAME[materialName]
3142
- if mat ~= nil then
3143
- part.Material = mat
3144
- end
3145
- end)
3146
- if variantName ~= nil and variantName ~= "" then
3147
- pcall(function()
3148
- part.MaterialVariant = variantName
3149
- end)
3150
- end
3151
- end
3152
- part.Parent = model
3153
- partCount += 1
3154
- end
3155
- model.Parent = parentInstance
3156
- modelCount += 1
3157
- totalParts += partCount
3158
- local _arg0 = {
3159
- name = name,
3160
- partCount = partCount,
3161
- modelPath = getInstancePath(model),
3162
- }
3163
- table.insert(models, _arg0)
3164
- end
3165
- return {
3166
- success = true,
3167
- modelCount = modelCount,
3168
- totalParts = totalParts,
3169
- models = models,
3170
- }
3171
- end)
3172
- if success and result then
3173
- finishRecording(recordingId, true)
3174
- return result
3175
- else
3176
- finishRecording(recordingId, false)
3177
- return {
3178
- error = `Failed to import scene: {result}`,
3179
- }
3180
- end
3181
- end
3182
- local function searchMaterials(requestData)
3183
- local _condition = (requestData.query)
3184
- if _condition == nil then
3185
- _condition = ""
3186
- end
3187
- local query = string.lower(_condition)
3188
- local _condition_1 = (requestData.maxResults)
3189
- if _condition_1 == nil then
3190
- _condition_1 = 50
3191
- end
3192
- local maxResults = _condition_1
3193
- local success, result = pcall(function()
3194
- local children = MaterialService:GetChildren()
3195
- local materials = {}
3196
- for _, child in children do
3197
- if not child:IsA("MaterialVariant") then
3198
- continue
3199
- end
3200
- local nameMatch = query == "" or (string.find(string.lower(child.Name), query)) ~= nil
3201
- if not nameMatch then
3202
- continue
3203
- end
3204
- local _arg0 = {
3205
- name = child.Name,
3206
- baseMaterial = child.BaseMaterial.Name,
3207
- }
3208
- table.insert(materials, _arg0)
3209
- if #materials >= maxResults then
3210
- break
3211
- end
3212
- end
3213
- return {
3214
- success = true,
3215
- materials = materials,
3216
- total = #materials,
3217
- }
3218
- end)
3219
- if success and result then
3220
- return result
3221
- else
3222
- return {
3223
- error = `Failed to search materials: {result}`,
3224
- }
3225
- end
3226
- end
3227
- return {
3228
- exportBuild = exportBuild,
3229
- importBuild = importBuild,
3230
- importScene = importScene,
3231
- searchMaterials = searchMaterials,
3232
- }
3233
- ]]></string>
3234
- </Properties>
3235
- </Item>
3236
- <Item class="ModuleScript" referent="10">
3237
2643
  <Properties>
3238
2644
  <string name="Name">CaptureHandlers</string>
3239
2645
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3448,7 +2854,7 @@ return {
3448
2854
  ]]></string>
3449
2855
  </Properties>
3450
2856
  </Item>
3451
- <Item class="ModuleScript" referent="11">
2857
+ <Item class="ModuleScript" referent="10">
3452
2858
  <Properties>
3453
2859
  <string name="Name">EvalRuntimeHandlers</string>
3454
2860
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3602,7 +3008,7 @@ return {
3602
3008
  ]]></string>
3603
3009
  </Properties>
3604
3010
  </Item>
3605
- <Item class="ModuleScript" referent="12">
3011
+ <Item class="ModuleScript" referent="11">
3606
3012
  <Properties>
3607
3013
  <string name="Name">GenerateModelHandlers</string>
3608
3014
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3807,7 +3213,7 @@ return {
3807
3213
  ]]></string>
3808
3214
  </Properties>
3809
3215
  </Item>
3810
- <Item class="ModuleScript" referent="13">
3216
+ <Item class="ModuleScript" referent="12">
3811
3217
  <Properties>
3812
3218
  <string name="Name">InputHandlers</string>
3813
3219
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4007,514 +3413,7 @@ return {
4007
3413
  ]]></string>
4008
3414
  </Properties>
4009
3415
  </Item>
4010
- <Item class="ModuleScript" referent="14">
4011
- <Properties>
4012
- <string name="Name">InstanceHandlers</string>
4013
- <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
4014
- local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
4015
- local Utils = TS.import(script, script.Parent.Parent, "Utils")
4016
- local Recording = TS.import(script, script.Parent.Parent, "Recording")
4017
- local _binding = Utils
4018
- local getInstancePath = _binding.getInstancePath
4019
- local getInstanceByPath = _binding.getInstanceByPath
4020
- local convertPropertyValue = _binding.convertPropertyValue
4021
- local _binding_1 = Recording
4022
- local beginRecording = _binding_1.beginRecording
4023
- local finishRecording = _binding_1.finishRecording
4024
- local function processObjectEntries(objects, createFn)
4025
- local results = {}
4026
- local successCount = 0
4027
- local failureCount = 0
4028
- local loopSuccess, loopError = pcall(function()
4029
- for _, entry in objects do
4030
- if not (type(entry) == "table") then
4031
- failureCount += 1
4032
- table.insert(results, {
4033
- success = false,
4034
- error = "Each object entry must be a table",
4035
- })
4036
- continue
4037
- end
4038
- local objData = entry
4039
- local className = objData.className
4040
- local parentPath = objData.parent
4041
- if not (className ~= "" and className) or not (parentPath ~= "" and parentPath) then
4042
- failureCount += 1
4043
- table.insert(results, {
4044
- success = false,
4045
- error = "Class name and parent are required",
4046
- })
4047
- continue
4048
- end
4049
- local entrySuccess, entryResult = pcall(function()
4050
- return createFn(objData)
4051
- end)
4052
- if not entrySuccess then
4053
- failureCount += 1
4054
- local _arg0 = {
4055
- success = false,
4056
- className = className,
4057
- parent = parentPath,
4058
- error = tostring(entryResult),
4059
- }
4060
- table.insert(results, _arg0)
4061
- continue
4062
- end
4063
- if entryResult.instance ~= nil then
4064
- successCount += 1
4065
- local _arg0 = {
4066
- success = true,
4067
- className = entryResult.className,
4068
- parent = entryResult.parentPath,
4069
- instancePath = getInstancePath(entryResult.instance),
4070
- name = entryResult.instance.Name,
4071
- }
4072
- table.insert(results, _arg0)
4073
- else
4074
- failureCount += 1
4075
- local _object = {
4076
- success = false,
4077
- }
4078
- local _left = "className"
4079
- local _condition = entryResult.className
4080
- if _condition == nil then
4081
- _condition = className
4082
- end
4083
- _object[_left] = _condition
4084
- local _left_1 = "parent"
4085
- local _condition_1 = entryResult.parentPath
4086
- if _condition_1 == nil then
4087
- _condition_1 = parentPath
4088
- end
4089
- _object[_left_1] = _condition_1
4090
- _object.error = entryResult.error
4091
- table.insert(results, _object)
4092
- end
4093
- end
4094
- end)
4095
- if not loopSuccess then
4096
- failureCount += 1
4097
- local _arg0 = {
4098
- success = false,
4099
- error = `Unexpected mass create failure: {tostring(loopError)}`,
4100
- }
4101
- table.insert(results, _arg0)
4102
- end
4103
- return {
4104
- results = results,
4105
- successCount = successCount,
4106
- failureCount = failureCount,
4107
- }
4108
- end
4109
- local function createObject(requestData)
4110
- local className = requestData.className
4111
- local parentPath = requestData.parent
4112
- local name = requestData.name
4113
- local properties = (requestData.properties) or {}
4114
- if not (className ~= "" and className) or not (parentPath ~= "" and parentPath) then
4115
- return {
4116
- error = "Class name and parent are required",
4117
- }
4118
- end
4119
- local parentInstance = getInstanceByPath(parentPath)
4120
- if not parentInstance then
4121
- return {
4122
- error = `Parent instance not found: {parentPath}`,
4123
- }
4124
- end
4125
- local recordingId = beginRecording(`Create {className}`)
4126
- local success, newInstance = pcall(function()
4127
- local instance = Instance.new(className)
4128
- if name ~= "" and name then
4129
- instance.Name = name
4130
- end
4131
- for propertyName, propertyValue in pairs(properties) do
4132
- pcall(function()
4133
- local converted = convertPropertyValue(instance, propertyName, propertyValue)
4134
- instance[propertyName] = if converted ~= nil then converted else propertyValue
4135
- end)
4136
- end
4137
- instance.Parent = parentInstance
4138
- return instance
4139
- end)
4140
- if success and newInstance then
4141
- finishRecording(recordingId, true)
4142
- return {
4143
- success = true,
4144
- className = className,
4145
- parent = parentPath,
4146
- instancePath = getInstancePath(newInstance),
4147
- name = newInstance.Name,
4148
- message = "Object created successfully",
4149
- }
4150
- else
4151
- finishRecording(recordingId, false)
4152
- return {
4153
- error = `Failed to create object: {newInstance}`,
4154
- className = className,
4155
- parent = parentPath,
4156
- }
4157
- end
4158
- end
4159
- local function deleteObject(requestData)
4160
- local instancePath = requestData.instancePath
4161
- if not (instancePath ~= "" and instancePath) then
4162
- return {
4163
- error = "Instance path is required",
4164
- }
4165
- end
4166
- local instance = getInstanceByPath(instancePath)
4167
- if not instance then
4168
- return {
4169
- error = `Instance not found: {instancePath}`,
4170
- }
4171
- end
4172
- if instance == game then
4173
- return {
4174
- error = "Cannot delete the game instance",
4175
- }
4176
- end
4177
- local recordingId = beginRecording(`Delete {instance.ClassName} ({instance.Name})`)
4178
- local success, result = pcall(function()
4179
- instance:Destroy()
4180
- return true
4181
- end)
4182
- if success then
4183
- finishRecording(recordingId, true)
4184
- return {
4185
- success = true,
4186
- instancePath = instancePath,
4187
- message = "Object deleted successfully",
4188
- }
4189
- else
4190
- finishRecording(recordingId, false)
4191
- return {
4192
- error = `Failed to delete object: {result}`,
4193
- instancePath = instancePath,
4194
- }
4195
- end
4196
- end
4197
- local function massCreateObjects(requestData)
4198
- local objects = requestData.objects
4199
- if not objects or not (type(objects) == "table") or #objects == 0 then
4200
- return {
4201
- error = "Objects array is required",
4202
- }
4203
- end
4204
- local recordingId = beginRecording("Mass create objects")
4205
- local _binding_2 = processObjectEntries(objects, function(objData)
4206
- local className = objData.className
4207
- local parentPath = objData.parent
4208
- local name = objData.name
4209
- local properties = (objData.properties) or {}
4210
- local parentInstance = getInstanceByPath(parentPath)
4211
- if not parentInstance then
4212
- return {
4213
- error = "Parent instance not found",
4214
- className = className,
4215
- parentPath = parentPath,
4216
- }
4217
- end
4218
- local success, newInstance = pcall(function()
4219
- local instance = Instance.new(className)
4220
- if name ~= "" and name then
4221
- instance.Name = name
4222
- end
4223
- for propertyName, propertyValue in pairs(properties) do
4224
- pcall(function()
4225
- local converted = convertPropertyValue(instance, propertyName, propertyValue)
4226
- instance[propertyName] = if converted ~= nil then converted else propertyValue
4227
- end)
4228
- end
4229
- instance.Parent = parentInstance
4230
- return instance
4231
- end)
4232
- if not success or not newInstance then
4233
- return {
4234
- error = tostring(newInstance),
4235
- className = className,
4236
- parentPath = parentPath,
4237
- }
4238
- end
4239
- return {
4240
- instance = newInstance,
4241
- className = className,
4242
- parentPath = parentPath,
4243
- }
4244
- end)
4245
- local results = _binding_2.results
4246
- local successCount = _binding_2.successCount
4247
- local failureCount = _binding_2.failureCount
4248
- finishRecording(recordingId, successCount > 0)
4249
- return {
4250
- results = results,
4251
- summary = {
4252
- total = #objects,
4253
- succeeded = successCount,
4254
- failed = failureCount,
4255
- },
4256
- }
4257
- end
4258
- local function performSmartDuplicate(requestData, useRecording)
4259
- if useRecording == nil then
4260
- useRecording = true
4261
- end
4262
- local instancePath = requestData.instancePath
4263
- local count = requestData.count
4264
- local options = (requestData.options) or {}
4265
- if not (instancePath ~= "" and instancePath) or not (count ~= 0 and count == count and count) or count < 1 then
4266
- return {
4267
- error = "Instance path and count > 0 are required",
4268
- }
4269
- end
4270
- local instance = getInstanceByPath(instancePath)
4271
- if not instance then
4272
- return {
4273
- error = `Instance not found: {instancePath}`,
4274
- }
4275
- end
4276
- local recordingId = if useRecording then beginRecording(`Smart duplicate {instance.Name}`) else nil
4277
- local results = {}
4278
- local successCount = 0
4279
- local failureCount = 0
4280
- do
4281
- local _i = 1
4282
- local _shouldIncrement = false
4283
- while true do
4284
- local i = _i
4285
- if _shouldIncrement then
4286
- i += 1
4287
- else
4288
- _shouldIncrement = true
4289
- end
4290
- if not (i <= count) then
4291
- break
4292
- end
4293
- local success, newInstance = pcall(function()
4294
- local clone = instance:Clone()
4295
- local _value = options.namePattern
4296
- if _value ~= 0 and _value == _value and _value ~= "" and _value then
4297
- local _exp = (options.namePattern)
4298
- local _arg1 = tostring(i)
4299
- clone.Name = (string.gsub(_exp, "{n}", _arg1))
4300
- else
4301
- clone.Name = instance.Name .. tostring(i)
4302
- end
4303
- local _condition = options.positionOffset
4304
- if _condition ~= 0 and _condition == _condition and _condition ~= "" and _condition then
4305
- _condition = clone:IsA("BasePart")
4306
- end
4307
- if _condition ~= 0 and _condition == _condition and _condition ~= "" and _condition then
4308
- local offset = options.positionOffset
4309
- local currentPos = clone.Position
4310
- local _exp = currentPos.X
4311
- local _condition_1 = offset[1]
4312
- if _condition_1 == nil then
4313
- _condition_1 = 0
4314
- end
4315
- local _exp_1 = _exp + _condition_1 * i
4316
- local _exp_2 = currentPos.Y
4317
- local _condition_2 = offset[2]
4318
- if _condition_2 == nil then
4319
- _condition_2 = 0
4320
- end
4321
- local _exp_3 = _exp_2 + _condition_2 * i
4322
- local _exp_4 = currentPos.Z
4323
- local _condition_3 = offset[3]
4324
- if _condition_3 == nil then
4325
- _condition_3 = 0
4326
- end
4327
- clone.Position = Vector3.new(_exp_1, _exp_3, _exp_4 + _condition_3 * i)
4328
- end
4329
- local _condition_1 = options.rotationOffset
4330
- if _condition_1 ~= 0 and _condition_1 == _condition_1 and _condition_1 ~= "" and _condition_1 then
4331
- _condition_1 = clone:IsA("BasePart")
4332
- end
4333
- if _condition_1 ~= 0 and _condition_1 == _condition_1 and _condition_1 ~= "" and _condition_1 then
4334
- local offset = options.rotationOffset
4335
- local _cFrame = clone.CFrame
4336
- local _condition_2 = offset[1]
4337
- if _condition_2 == nil then
4338
- _condition_2 = 0
4339
- end
4340
- local _exp = math.rad(_condition_2 * i)
4341
- local _condition_3 = offset[2]
4342
- if _condition_3 == nil then
4343
- _condition_3 = 0
4344
- end
4345
- local _exp_1 = math.rad(_condition_3 * i)
4346
- local _condition_4 = offset[3]
4347
- if _condition_4 == nil then
4348
- _condition_4 = 0
4349
- end
4350
- local _arg0 = CFrame.Angles(_exp, _exp_1, math.rad(_condition_4 * i))
4351
- clone.CFrame = _cFrame * _arg0
4352
- end
4353
- local _condition_2 = options.scaleOffset
4354
- if _condition_2 ~= 0 and _condition_2 == _condition_2 and _condition_2 ~= "" and _condition_2 then
4355
- _condition_2 = clone:IsA("BasePart")
4356
- end
4357
- if _condition_2 ~= 0 and _condition_2 == _condition_2 and _condition_2 ~= "" and _condition_2 then
4358
- local offset = options.scaleOffset
4359
- local currentSize = clone.Size
4360
- local _exp = currentSize.X
4361
- local _condition_3 = offset[1]
4362
- if _condition_3 == nil then
4363
- _condition_3 = 1
4364
- end
4365
- local _exp_1 = _exp * (_condition_3 ^ i)
4366
- local _exp_2 = currentSize.Y
4367
- local _condition_4 = offset[2]
4368
- if _condition_4 == nil then
4369
- _condition_4 = 1
4370
- end
4371
- local _exp_3 = _exp_2 * (_condition_4 ^ i)
4372
- local _exp_4 = currentSize.Z
4373
- local _condition_5 = offset[3]
4374
- if _condition_5 == nil then
4375
- _condition_5 = 1
4376
- end
4377
- clone.Size = Vector3.new(_exp_1, _exp_3, _exp_4 * (_condition_5 ^ i))
4378
- end
4379
- local _value_1 = options.propertyVariations
4380
- if _value_1 ~= 0 and _value_1 == _value_1 and _value_1 ~= "" and _value_1 then
4381
- for propName, values in pairs(options.propertyVariations) do
4382
- if values and #values > 0 then
4383
- local valueIndex = ((i - 1) % #values)
4384
- pcall(function()
4385
- clone[propName] = values[valueIndex + 1]
4386
- end)
4387
- end
4388
- end
4389
- end
4390
- local targetParents = options.targetParents
4391
- local _value_2 = targetParents and targetParents[i]
4392
- if _value_2 ~= "" and _value_2 then
4393
- local targetParent = getInstanceByPath(targetParents[i])
4394
- clone.Parent = targetParent or instance.Parent
4395
- else
4396
- clone.Parent = instance.Parent
4397
- end
4398
- return clone
4399
- end)
4400
- if success and newInstance then
4401
- successCount += 1
4402
- local _arg0 = {
4403
- success = true,
4404
- instancePath = getInstancePath(newInstance),
4405
- name = newInstance.Name,
4406
- index = i,
4407
- }
4408
- table.insert(results, _arg0)
4409
- else
4410
- failureCount += 1
4411
- local _arg0 = {
4412
- success = false,
4413
- index = i,
4414
- error = tostring(newInstance),
4415
- }
4416
- table.insert(results, _arg0)
4417
- end
4418
- _i = i
4419
- end
4420
- end
4421
- finishRecording(recordingId, successCount > 0)
4422
- return {
4423
- results = results,
4424
- summary = {
4425
- total = count,
4426
- succeeded = successCount,
4427
- failed = failureCount,
4428
- },
4429
- sourceInstance = instancePath,
4430
- }
4431
- end
4432
- local function smartDuplicate(requestData)
4433
- return performSmartDuplicate(requestData, true)
4434
- end
4435
- local function massDuplicate(requestData)
4436
- local duplications = requestData.duplications
4437
- if not duplications or not (type(duplications) == "table") or #duplications == 0 then
4438
- return {
4439
- error = "Duplications array is required",
4440
- }
4441
- end
4442
- local allResults = {}
4443
- local totalSuccess = 0
4444
- local totalFailures = 0
4445
- local recordingId = beginRecording("Mass duplicate operations")
4446
- for _, duplication in duplications do
4447
- local result = performSmartDuplicate(duplication, false)
4448
- table.insert(allResults, result)
4449
- if result.summary then
4450
- totalSuccess += result.summary.succeeded
4451
- totalFailures += result.summary.failed
4452
- end
4453
- end
4454
- finishRecording(recordingId, totalSuccess > 0)
4455
- return {
4456
- results = allResults,
4457
- summary = {
4458
- total = totalSuccess + totalFailures,
4459
- succeeded = totalSuccess,
4460
- failed = totalFailures,
4461
- },
4462
- }
4463
- end
4464
- local function cloneObject(requestData)
4465
- local instancePath = requestData.instancePath
4466
- local targetParentPath = requestData.targetParentPath
4467
- if not (instancePath ~= "" and instancePath) or not (targetParentPath ~= "" and targetParentPath) then
4468
- return {
4469
- error = "Instance path and target parent path are required",
4470
- }
4471
- end
4472
- local instance = getInstanceByPath(instancePath)
4473
- if not instance then
4474
- return {
4475
- error = `Instance not found: {instancePath}`,
4476
- }
4477
- end
4478
- local targetParent = getInstanceByPath(targetParentPath)
4479
- if not targetParent then
4480
- return {
4481
- error = `Target parent not found: {targetParentPath}`,
4482
- }
4483
- end
4484
- local recordingId = beginRecording(`Clone {instance.Name}`)
4485
- local success, clone = pcall(function()
4486
- local cloned = instance:Clone()
4487
- cloned.Parent = targetParent
4488
- return cloned
4489
- end)
4490
- if success and clone then
4491
- finishRecording(recordingId, true)
4492
- return {
4493
- success = true,
4494
- instancePath = getInstancePath(clone),
4495
- name = clone.Name,
4496
- className = clone.ClassName,
4497
- parent = targetParentPath,
4498
- message = "Object cloned successfully",
4499
- }
4500
- end
4501
- finishRecording(recordingId, false)
4502
- return {
4503
- error = `Failed to clone object: {clone}`,
4504
- }
4505
- end
4506
- return {
4507
- createObject = createObject,
4508
- deleteObject = deleteObject,
4509
- massCreateObjects = massCreateObjects,
4510
- smartDuplicate = smartDuplicate,
4511
- massDuplicate = massDuplicate,
4512
- cloneObject = cloneObject,
4513
- }
4514
- ]]></string>
4515
- </Properties>
4516
- </Item>
4517
- <Item class="ModuleScript" referent="15">
3416
+ <Item class="ModuleScript" referent="13">
4518
3417
  <Properties>
4519
3418
  <string name="Name">LogHandlers</string>
4520
3419
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4540,7 +3439,7 @@ return {
4540
3439
  ]]></string>
4541
3440
  </Properties>
4542
3441
  </Item>
4543
- <Item class="ModuleScript" referent="16">
3442
+ <Item class="ModuleScript" referent="14">
4544
3443
  <Properties>
4545
3444
  <string name="Name">MemoryHandlers</string>
4546
3445
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4608,23 +3507,17 @@ return {
4608
3507
  ]]></string>
4609
3508
  </Properties>
4610
3509
  </Item>
4611
- <Item class="ModuleScript" referent="17">
3510
+ <Item class="ModuleScript" referent="15">
4612
3511
  <Properties>
4613
3512
  <string name="Name">MetadataHandlers</string>
4614
3513
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
4615
3514
  local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
4616
- local CollectionService = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services").CollectionService
4617
3515
  local Utils = TS.import(script, script.Parent.Parent, "Utils")
4618
- local Recording = TS.import(script, script.Parent.Parent, "Recording")
4619
3516
  local LuauExec = TS.import(script, script.Parent.Parent, "LuauExec")
4620
- local ChangeHistoryService = game:GetService("ChangeHistoryService")
4621
3517
  local Selection = game:GetService("Selection")
4622
3518
  local _binding = Utils
4623
3519
  local getInstancePath = _binding.getInstancePath
4624
3520
  local getInstanceByPath = _binding.getInstanceByPath
4625
- local _binding_1 = Recording
4626
- local beginRecording = _binding_1.beginRecording
4627
- local finishRecording = _binding_1.finishRecording
4628
3521
  local function serializeValue(value)
4629
3522
  local _value = value
4630
3523
  local vType = typeof(_value)
@@ -4676,127 +3569,6 @@ local function serializeValue(value)
4676
3569
  end
4677
3570
  return value
4678
3571
  end
4679
- local function deserializeValue(attributeValue, valueType)
4680
- local _attributeValue = attributeValue
4681
- if not (type(_attributeValue) == "table") then
4682
- return attributeValue
4683
- end
4684
- local tbl = attributeValue
4685
- local _condition = (tbl._type)
4686
- if _condition == nil then
4687
- _condition = valueType
4688
- end
4689
- local t = _condition
4690
- if t == "Vector3" then
4691
- local _condition_1 = (tbl.X)
4692
- if _condition_1 == nil then
4693
- _condition_1 = 0
4694
- end
4695
- local _condition_2 = (tbl.Y)
4696
- if _condition_2 == nil then
4697
- _condition_2 = 0
4698
- end
4699
- local _condition_3 = (tbl.Z)
4700
- if _condition_3 == nil then
4701
- _condition_3 = 0
4702
- end
4703
- return Vector3.new(_condition_1, _condition_2, _condition_3)
4704
- elseif t == "Color3" then
4705
- local _condition_1 = (tbl.R)
4706
- if _condition_1 == nil then
4707
- _condition_1 = 0
4708
- end
4709
- local _condition_2 = (tbl.G)
4710
- if _condition_2 == nil then
4711
- _condition_2 = 0
4712
- end
4713
- local _condition_3 = (tbl.B)
4714
- if _condition_3 == nil then
4715
- _condition_3 = 0
4716
- end
4717
- return Color3.new(_condition_1, _condition_2, _condition_3)
4718
- elseif t == "UDim2" then
4719
- local x = tbl.X
4720
- local y = tbl.Y
4721
- local _result = x
4722
- if _result ~= nil then
4723
- _result = _result.Scale
4724
- end
4725
- local _condition_1 = _result
4726
- if _condition_1 == nil then
4727
- _condition_1 = 0
4728
- end
4729
- local _result_1 = x
4730
- if _result_1 ~= nil then
4731
- _result_1 = _result_1.Offset
4732
- end
4733
- local _condition_2 = _result_1
4734
- if _condition_2 == nil then
4735
- _condition_2 = 0
4736
- end
4737
- local _result_2 = y
4738
- if _result_2 ~= nil then
4739
- _result_2 = _result_2.Scale
4740
- end
4741
- local _condition_3 = _result_2
4742
- if _condition_3 == nil then
4743
- _condition_3 = 0
4744
- end
4745
- local _result_3 = y
4746
- if _result_3 ~= nil then
4747
- _result_3 = _result_3.Offset
4748
- end
4749
- local _condition_4 = _result_3
4750
- if _condition_4 == nil then
4751
- _condition_4 = 0
4752
- end
4753
- return UDim2.new(_condition_1, _condition_2, _condition_3, _condition_4)
4754
- elseif t == "BrickColor" then
4755
- local _condition_1 = (tbl.Name)
4756
- if _condition_1 == nil then
4757
- _condition_1 = "Medium stone grey"
4758
- end
4759
- return BrickColor.new(_condition_1)
4760
- end
4761
- return attributeValue
4762
- end
4763
- local function setAttribute(requestData)
4764
- local instancePath = requestData.instancePath
4765
- local attributeName = requestData.attributeName
4766
- local attributeValue = requestData.attributeValue
4767
- local valueType = requestData.valueType
4768
- if not (instancePath ~= "" and instancePath) or not (attributeName ~= "" and attributeName) then
4769
- return {
4770
- error = "Instance path and attribute name are required",
4771
- }
4772
- end
4773
- local instance = getInstanceByPath(instancePath)
4774
- if not instance then
4775
- return {
4776
- error = `Instance not found: {instancePath}`,
4777
- }
4778
- end
4779
- local recordingId = beginRecording(`Set attribute {attributeName} on {instance.Name}`)
4780
- local success, result = pcall(function()
4781
- local value = deserializeValue(attributeValue, valueType)
4782
- instance:SetAttribute(attributeName, value)
4783
- return {
4784
- success = true,
4785
- instancePath = instancePath,
4786
- attributeName = attributeName,
4787
- value = attributeValue,
4788
- message = "Attribute set successfully",
4789
- }
4790
- end)
4791
- if success then
4792
- finishRecording(recordingId, true)
4793
- return result
4794
- end
4795
- finishRecording(recordingId, false)
4796
- return {
4797
- error = `Failed to set attribute: {result}`,
4798
- }
4799
- end
4800
3572
  local function getAttributes(requestData)
4801
3573
  local instancePath = requestData.instancePath
4802
3574
  if not (instancePath ~= "" and instancePath) then
@@ -4834,175 +3606,6 @@ local function getAttributes(requestData)
4834
3606
  error = `Failed to get attributes: {result}`,
4835
3607
  }
4836
3608
  end
4837
- local function deleteAttribute(requestData)
4838
- local instancePath = requestData.instancePath
4839
- local attributeName = requestData.attributeName
4840
- if not (instancePath ~= "" and instancePath) or not (attributeName ~= "" and attributeName) then
4841
- return {
4842
- error = "Instance path and attribute name are required",
4843
- }
4844
- end
4845
- local instance = getInstanceByPath(instancePath)
4846
- if not instance then
4847
- return {
4848
- error = `Instance not found: {instancePath}`,
4849
- }
4850
- end
4851
- local recordingId = beginRecording(`Delete attribute {attributeName} from {instance.Name}`)
4852
- local success, result = pcall(function()
4853
- local existed = instance:GetAttribute(attributeName) ~= nil
4854
- instance:SetAttribute(attributeName, nil)
4855
- return {
4856
- success = true,
4857
- instancePath = instancePath,
4858
- attributeName = attributeName,
4859
- existed = existed,
4860
- message = if existed then "Attribute deleted successfully" else "Attribute did not exist",
4861
- }
4862
- end)
4863
- if success then
4864
- finishRecording(recordingId, true)
4865
- return result
4866
- end
4867
- finishRecording(recordingId, false)
4868
- return {
4869
- error = `Failed to delete attribute: {result}`,
4870
- }
4871
- end
4872
- local function getTags(requestData)
4873
- local instancePath = requestData.instancePath
4874
- if not (instancePath ~= "" and instancePath) then
4875
- return {
4876
- error = "Instance path is required",
4877
- }
4878
- end
4879
- local instance = getInstanceByPath(instancePath)
4880
- if not instance then
4881
- return {
4882
- error = `Instance not found: {instancePath}`,
4883
- }
4884
- end
4885
- local success, result = pcall(function()
4886
- local tags = CollectionService:GetTags(instance)
4887
- return {
4888
- instancePath = instancePath,
4889
- tags = tags,
4890
- count = #tags,
4891
- }
4892
- end)
4893
- if success then
4894
- return result
4895
- end
4896
- return {
4897
- error = `Failed to get tags: {result}`,
4898
- }
4899
- end
4900
- local function addTag(requestData)
4901
- local instancePath = requestData.instancePath
4902
- local tagName = requestData.tagName
4903
- if not (instancePath ~= "" and instancePath) or not (tagName ~= "" and tagName) then
4904
- return {
4905
- error = "Instance path and tag name are required",
4906
- }
4907
- end
4908
- local instance = getInstanceByPath(instancePath)
4909
- if not instance then
4910
- return {
4911
- error = `Instance not found: {instancePath}`,
4912
- }
4913
- end
4914
- local recordingId = beginRecording(`Add tag {tagName} to {instance.Name}`)
4915
- local success, result = pcall(function()
4916
- local alreadyHad = CollectionService:HasTag(instance, tagName)
4917
- CollectionService:AddTag(instance, tagName)
4918
- return {
4919
- success = true,
4920
- instancePath = instancePath,
4921
- tagName = tagName,
4922
- alreadyHad = alreadyHad,
4923
- message = if alreadyHad then "Instance already had this tag" else "Tag added successfully",
4924
- }
4925
- end)
4926
- if success then
4927
- finishRecording(recordingId, true)
4928
- return result
4929
- end
4930
- finishRecording(recordingId, false)
4931
- return {
4932
- error = `Failed to add tag: {result}`,
4933
- }
4934
- end
4935
- local function removeTag(requestData)
4936
- local instancePath = requestData.instancePath
4937
- local tagName = requestData.tagName
4938
- if not (instancePath ~= "" and instancePath) or not (tagName ~= "" and tagName) then
4939
- return {
4940
- error = "Instance path and tag name are required",
4941
- }
4942
- end
4943
- local instance = getInstanceByPath(instancePath)
4944
- if not instance then
4945
- return {
4946
- error = `Instance not found: {instancePath}`,
4947
- }
4948
- end
4949
- local recordingId = beginRecording(`Remove tag {tagName} from {instance.Name}`)
4950
- local success, result = pcall(function()
4951
- local hadTag = CollectionService:HasTag(instance, tagName)
4952
- CollectionService:RemoveTag(instance, tagName)
4953
- return {
4954
- success = true,
4955
- instancePath = instancePath,
4956
- tagName = tagName,
4957
- hadTag = hadTag,
4958
- message = if hadTag then "Tag removed successfully" else "Instance did not have this tag",
4959
- }
4960
- end)
4961
- if success then
4962
- finishRecording(recordingId, true)
4963
- return result
4964
- end
4965
- finishRecording(recordingId, false)
4966
- return {
4967
- error = `Failed to remove tag: {result}`,
4968
- }
4969
- end
4970
- local function getTagged(requestData)
4971
- local tagName = requestData.tagName
4972
- if not (tagName ~= "" and tagName) then
4973
- return {
4974
- error = "Tag name is required",
4975
- }
4976
- end
4977
- local success, result = pcall(function()
4978
- local taggedInstances = CollectionService:GetTagged(tagName)
4979
- -- ▼ ReadonlyArray.map ▼
4980
- local _newValue = table.create(#taggedInstances)
4981
- local _callback = function(instance)
4982
- return {
4983
- name = instance.Name,
4984
- className = instance.ClassName,
4985
- path = getInstancePath(instance),
4986
- }
4987
- end
4988
- for _k, _v in taggedInstances do
4989
- _newValue[_k] = _callback(_v, _k - 1, taggedInstances)
4990
- end
4991
- -- ▲ ReadonlyArray.map ▲
4992
- local instances = _newValue
4993
- return {
4994
- tagName = tagName,
4995
- instances = instances,
4996
- count = #instances,
4997
- }
4998
- end)
4999
- if success then
5000
- return result
5001
- end
5002
- return {
5003
- error = `Failed to get tagged instances: {result}`,
5004
- }
5005
- end
5006
3609
  local function getSelection(_requestData)
5007
3610
  local selection = Selection:Get()
5008
3611
  if #selection == 0 then
@@ -5048,106 +3651,15 @@ local function executeLuau(requestData)
5048
3651
  -- the same code path and produce identical output shapes.
5049
3652
  return LuauExec.execute(code)
5050
3653
  end
5051
- local function undo(_requestData)
5052
- local success, result = pcall(function()
5053
- ChangeHistoryService:Undo()
5054
- return {
5055
- success = true,
5056
- message = "Undo executed successfully",
5057
- }
5058
- end)
5059
- if success then
5060
- return result
5061
- end
5062
- return {
5063
- error = `Failed to undo: {result}`,
5064
- }
5065
- end
5066
- local function redo(_requestData)
5067
- local success, result = pcall(function()
5068
- ChangeHistoryService:Redo()
5069
- return {
5070
- success = true,
5071
- message = "Redo executed successfully",
5072
- }
5073
- end)
5074
- if success then
5075
- return result
5076
- end
5077
- return {
5078
- error = `Failed to redo: {result}`,
5079
- }
5080
- end
5081
- local function bulkSetAttributes(requestData)
5082
- local instancePath = requestData.instancePath
5083
- local attributes = requestData.attributes
5084
- if not (instancePath ~= "" and instancePath) or not attributes then
5085
- return {
5086
- error = "Instance path and attributes are required",
5087
- }
5088
- end
5089
- local instance = getInstanceByPath(instancePath)
5090
- if not instance then
5091
- return {
5092
- error = `Instance not found: {instancePath}`,
5093
- }
5094
- end
5095
- local recordingId = beginRecording(`Bulk set attributes on {instance.Name}`)
5096
- local results = {}
5097
- local successCount = 0
5098
- local failureCount = 0
5099
- for name, rawValue in pairs(attributes) do
5100
- local attrName = name
5101
- local ok, err = pcall(function()
5102
- local value = deserializeValue(rawValue)
5103
- instance:SetAttribute(attrName, value)
5104
- end)
5105
- if ok then
5106
- successCount += 1
5107
- local _arg0 = {
5108
- attributeName = attrName,
5109
- success = true,
5110
- }
5111
- table.insert(results, _arg0)
5112
- else
5113
- failureCount += 1
5114
- local _arg0 = {
5115
- attributeName = attrName,
5116
- success = false,
5117
- error = tostring(err),
5118
- }
5119
- table.insert(results, _arg0)
5120
- end
5121
- end
5122
- finishRecording(recordingId, successCount > 0)
5123
- return {
5124
- instancePath = instancePath,
5125
- results = results,
5126
- summary = {
5127
- total = successCount + failureCount,
5128
- succeeded = successCount,
5129
- failed = failureCount,
5130
- },
5131
- }
5132
- end
5133
3654
  return {
5134
- setAttribute = setAttribute,
5135
3655
  getAttributes = getAttributes,
5136
- deleteAttribute = deleteAttribute,
5137
- getTags = getTags,
5138
- addTag = addTag,
5139
- removeTag = removeTag,
5140
- getTagged = getTagged,
5141
3656
  getSelection = getSelection,
5142
3657
  executeLuau = executeLuau,
5143
- undo = undo,
5144
- redo = redo,
5145
- bulkSetAttributes = bulkSetAttributes,
5146
3658
  }
5147
3659
  ]]></string>
5148
3660
  </Properties>
5149
3661
  </Item>
5150
- <Item class="ModuleScript" referent="18">
3662
+ <Item class="ModuleScript" referent="16">
5151
3663
  <Properties>
5152
3664
  <string name="Name">MicroProfilerHandlers</string>
5153
3665
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6791,7 +5303,7 @@ return {
6791
5303
  ]]></string>
6792
5304
  </Properties>
6793
5305
  </Item>
6794
- <Item class="ModuleScript" referent="19">
5306
+ <Item class="ModuleScript" referent="17">
6795
5307
  <Properties>
6796
5308
  <string name="Name">PropertyHandlers</string>
6797
5309
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6804,170 +5316,6 @@ local convertPropertyValue = _binding.convertPropertyValue
6804
5316
  local _binding_1 = Recording
6805
5317
  local beginRecording = _binding_1.beginRecording
6806
5318
  local finishRecording = _binding_1.finishRecording
6807
- local function setProperty(requestData)
6808
- local instancePath = requestData.instancePath
6809
- local propertyName = requestData.propertyName
6810
- local propertyValue = requestData.propertyValue
6811
- if not (instancePath ~= "" and instancePath) or not (propertyName ~= "" and propertyName) then
6812
- return {
6813
- error = "Instance path and property name are required",
6814
- }
6815
- end
6816
- local instance = getInstanceByPath(instancePath)
6817
- if not instance then
6818
- return {
6819
- error = `Instance not found: {instancePath}`,
6820
- }
6821
- end
6822
- local recordingId = beginRecording(`Set {propertyName} property`)
6823
- local inst = instance
6824
- local success, result = pcall(function()
6825
- if propertyName == "Parent" or propertyName == "PrimaryPart" then
6826
- if type(propertyValue) == "string" then
6827
- local refInstance = getInstanceByPath(propertyValue)
6828
- if refInstance then
6829
- inst[propertyName] = refInstance
6830
- else
6831
- return {
6832
- error = `{propertyName} instance not found: {propertyValue}`,
6833
- }
6834
- end
6835
- end
6836
- elseif propertyName == "Name" then
6837
- instance.Name = tostring(propertyValue)
6838
- elseif propertyName == "Source" and instance:IsA("LuaSourceContainer") then
6839
- instance.Source = tostring(propertyValue)
6840
- else
6841
- local convertedValue = convertPropertyValue(instance, propertyName, propertyValue)
6842
- if convertedValue ~= nil then
6843
- inst[propertyName] = convertedValue
6844
- else
6845
- inst[propertyName] = propertyValue
6846
- end
6847
- end
6848
- return true
6849
- end)
6850
- if success then
6851
- finishRecording(recordingId, true)
6852
- return {
6853
- success = true,
6854
- instancePath = instancePath,
6855
- propertyName = propertyName,
6856
- propertyValue = propertyValue,
6857
- message = "Property set successfully",
6858
- }
6859
- else
6860
- finishRecording(recordingId, false)
6861
- return {
6862
- error = `Failed to set property: {result}`,
6863
- instancePath = instancePath,
6864
- propertyName = propertyName,
6865
- }
6866
- end
6867
- end
6868
- local function massSetProperty(requestData)
6869
- local paths = requestData.paths
6870
- local propertyName = requestData.propertyName
6871
- local propertyValue = requestData.propertyValue
6872
- if not paths or not (type(paths) == "table") or #paths == 0 or not (propertyName ~= "" and propertyName) then
6873
- return {
6874
- error = "Paths array and property name are required",
6875
- }
6876
- end
6877
- local results = {}
6878
- local successCount = 0
6879
- local failureCount = 0
6880
- local recordingId = beginRecording(`Mass set {propertyName} property`)
6881
- for _, path in paths do
6882
- local instance = getInstanceByPath(path)
6883
- if instance then
6884
- local success, err = pcall(function()
6885
- local converted = convertPropertyValue(instance, propertyName, propertyValue)
6886
- instance[propertyName] = if converted ~= nil then converted else propertyValue
6887
- end)
6888
- if success then
6889
- successCount += 1
6890
- local _arg0 = {
6891
- path = path,
6892
- success = true,
6893
- propertyName = propertyName,
6894
- propertyValue = propertyValue,
6895
- }
6896
- table.insert(results, _arg0)
6897
- else
6898
- failureCount += 1
6899
- local _arg0 = {
6900
- path = path,
6901
- success = false,
6902
- error = tostring(err),
6903
- }
6904
- table.insert(results, _arg0)
6905
- end
6906
- else
6907
- failureCount += 1
6908
- local _arg0 = {
6909
- path = path,
6910
- success = false,
6911
- error = "Instance not found",
6912
- }
6913
- table.insert(results, _arg0)
6914
- end
6915
- end
6916
- finishRecording(recordingId, successCount > 0)
6917
- return {
6918
- results = results,
6919
- summary = {
6920
- total = #paths,
6921
- succeeded = successCount,
6922
- failed = failureCount,
6923
- },
6924
- }
6925
- end
6926
- local function massGetProperty(requestData)
6927
- local paths = requestData.paths
6928
- local propertyName = requestData.propertyName
6929
- if not paths or not (type(paths) == "table") or #paths == 0 or not (propertyName ~= "" and propertyName) then
6930
- return {
6931
- error = "Paths array and property name are required",
6932
- }
6933
- end
6934
- local results = {}
6935
- for _, path in paths do
6936
- local instance = getInstanceByPath(path)
6937
- if instance then
6938
- local success, value = pcall(function()
6939
- return instance[propertyName]
6940
- end)
6941
- if success then
6942
- local _arg0 = {
6943
- path = path,
6944
- success = true,
6945
- propertyName = propertyName,
6946
- propertyValue = value,
6947
- }
6948
- table.insert(results, _arg0)
6949
- else
6950
- local _arg0 = {
6951
- path = path,
6952
- success = false,
6953
- error = tostring(value),
6954
- }
6955
- table.insert(results, _arg0)
6956
- end
6957
- else
6958
- local _arg0 = {
6959
- path = path,
6960
- success = false,
6961
- error = "Instance not found",
6962
- }
6963
- table.insert(results, _arg0)
6964
- end
6965
- end
6966
- return {
6967
- results = results,
6968
- propertyName = propertyName,
6969
- }
6970
- end
6971
5319
  local function setProperties(requestData)
6972
5320
  local instancePath = requestData.instancePath
6973
5321
  local properties = requestData.properties
@@ -7035,15 +5383,12 @@ local function setProperties(requestData)
7035
5383
  }
7036
5384
  end
7037
5385
  return {
7038
- setProperty = setProperty,
7039
- massSetProperty = massSetProperty,
7040
- massGetProperty = massGetProperty,
7041
5386
  setProperties = setProperties,
7042
5387
  }
7043
5388
  ]]></string>
7044
5389
  </Properties>
7045
5390
  </Item>
7046
- <Item class="ModuleScript" referent="20">
5391
+ <Item class="ModuleScript" referent="18">
7047
5392
  <Properties>
7048
5393
  <string name="Name">QueryHandlers</string>
7049
5394
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7177,48 +5522,6 @@ local function getPlaceInfo(_requestData)
7177
5522
  },
7178
5523
  }
7179
5524
  end
7180
- local function getServices(requestData)
7181
- local serviceName = requestData.serviceName
7182
- if serviceName ~= "" and serviceName then
7183
- local ok, service = pcall(function()
7184
- return game:GetService(serviceName)
7185
- end)
7186
- if ok and service then
7187
- return {
7188
- service = {
7189
- name = service.Name,
7190
- className = service.ClassName,
7191
- path = getInstancePath(service),
7192
- childCount = #service:GetChildren(),
7193
- },
7194
- }
7195
- else
7196
- return {
7197
- error = `Service not found: {serviceName}`,
7198
- }
7199
- end
7200
- else
7201
- local services = {}
7202
- local commonServices = { "Workspace", "Players", "StarterGui", "StarterPack", "StarterPlayer", "ReplicatedStorage", "ServerStorage", "ServerScriptService", "HttpService", "TeleportService", "DataStoreService" }
7203
- for _, svcName in commonServices do
7204
- local ok, service = pcall(function()
7205
- return game:GetService(svcName)
7206
- end)
7207
- if ok and service then
7208
- local _arg0 = {
7209
- name = service.Name,
7210
- className = service.ClassName,
7211
- path = getInstancePath(service),
7212
- childCount = #service:GetChildren(),
7213
- }
7214
- table.insert(services, _arg0)
7215
- end
7216
- end
7217
- return {
7218
- services = services,
7219
- }
7220
- end
7221
- end
7222
5525
  local function searchObjects(requestData)
7223
5526
  local query = requestData.query
7224
5527
  local _condition = (requestData.searchType)
@@ -7397,39 +5700,6 @@ local function getInstanceProperties(requestData)
7397
5700
  }
7398
5701
  end
7399
5702
  end
7400
- local function getInstanceChildren(requestData)
7401
- local instancePath = requestData.instancePath
7402
- if not (instancePath ~= "" and instancePath) then
7403
- return {
7404
- error = "Instance path is required",
7405
- }
7406
- end
7407
- local instance = getInstanceByPath(instancePath)
7408
- if not instance then
7409
- return {
7410
- error = `Instance not found: {instancePath}`,
7411
- }
7412
- end
7413
- local children = {}
7414
- for _, child in instance:GetChildren() do
7415
- local entry = {
7416
- name = child.Name,
7417
- className = child.ClassName,
7418
- path = getInstancePath(child),
7419
- hasChildren = #child:GetChildren() > 0,
7420
- hasSource = child:IsA("LuaSourceContainer"),
7421
- }
7422
- if child:IsA("BaseScript") then
7423
- entry.enabled = child.Enabled
7424
- end
7425
- table.insert(children, entry)
7426
- end
7427
- return {
7428
- instancePath = instancePath,
7429
- children = children,
7430
- count = #children,
7431
- }
7432
- end
7433
5703
  local function searchByProperty(requestData)
7434
5704
  local propertyName = requestData.propertyName
7435
5705
  local propertyValue = requestData.propertyValue
@@ -7974,133 +6244,21 @@ local function grepScripts(requestData)
7974
6244
  },
7975
6245
  }
7976
6246
  end
7977
- local function getDescendants(requestData)
7978
- local instancePath = requestData.instancePath
7979
- if not (instancePath ~= "" and instancePath) then
7980
- return {
7981
- error = "Instance path is required",
7982
- }
7983
- end
7984
- local _condition = (requestData.maxDepth)
7985
- if _condition == nil then
7986
- _condition = 10
7987
- end
7988
- local maxDepth = _condition
7989
- local classFilter = requestData.classFilter
7990
- local instance = getInstanceByPath(instancePath)
7991
- if not instance then
7992
- return {
7993
- error = `Instance not found: {instancePath}`,
7994
- }
7995
- end
7996
- local descendants = {}
7997
- local function collect(inst, depth)
7998
- if depth > maxDepth then
7999
- return nil
8000
- end
8001
- for _, child in inst:GetChildren() do
8002
- local _condition_1 = classFilter
8003
- if _condition_1 ~= "" and _condition_1 then
8004
- _condition_1 = not child:IsA(classFilter)
8005
- end
8006
- if _condition_1 ~= "" and _condition_1 then
8007
- continue
8008
- end
8009
- local _arg0 = {
8010
- name = child.Name,
8011
- className = child.ClassName,
8012
- path = getInstancePath(child),
8013
- depth = depth,
8014
- }
8015
- table.insert(descendants, _arg0)
8016
- collect(child, depth + 1)
8017
- end
8018
- end
8019
- collect(instance, 1)
8020
- return {
8021
- instancePath = instancePath,
8022
- descendants = descendants,
8023
- count = #descendants,
8024
- maxDepth = maxDepth,
8025
- }
8026
- end
8027
- local function compareInstances(requestData)
8028
- local instancePathA = requestData.instancePathA
8029
- local instancePathB = requestData.instancePathB
8030
- if not (instancePathA ~= "" and instancePathA) or not (instancePathB ~= "" and instancePathB) then
8031
- return {
8032
- error = "Both instancePathA and instancePathB are required",
8033
- }
8034
- end
8035
- local instA = getInstanceByPath(instancePathA)
8036
- if not instA then
8037
- return {
8038
- error = `Instance not found: {instancePathA}`,
8039
- }
8040
- end
8041
- local instB = getInstanceByPath(instancePathB)
8042
- if not instB then
8043
- return {
8044
- error = `Instance not found: {instancePathB}`,
8045
- }
8046
- end
8047
- local commonProps = { "Name", "ClassName", "Size", "Position", "Rotation", "CFrame", "Anchored", "CanCollide", "Transparency", "BrickColor", "Material", "Color", "Text", "TextColor3", "BackgroundColor3", "Image", "ImageColor3", "Visible", "Active", "ZIndex", "BorderSizePixel", "BackgroundTransparency", "ImageTransparency", "TextTransparency", "Value", "Enabled", "Brightness", "Range", "Shadows" }
8048
- local matching = {}
8049
- local differing = {}
8050
- local onlyA = {}
8051
- local onlyB = {}
8052
- for _, prop in commonProps do
8053
- local okA, valA = pcall(function()
8054
- return tostring(instA[prop])
8055
- end)
8056
- local okB, valB = pcall(function()
8057
- return tostring(instB[prop])
8058
- end)
8059
- if okA and okB then
8060
- if valA == valB then
8061
- matching[prop] = valA
8062
- else
8063
- differing[prop] = {
8064
- a = valA,
8065
- b = valB,
8066
- }
8067
- end
8068
- elseif okA then
8069
- table.insert(onlyA, prop)
8070
- elseif okB then
8071
- table.insert(onlyB, prop)
8072
- end
8073
- end
8074
- return {
8075
- instancePathA = instancePathA,
8076
- instancePathB = instancePathB,
8077
- classNameA = instA.ClassName,
8078
- classNameB = instB.ClassName,
8079
- matching = matching,
8080
- differing = differing,
8081
- onlyA = onlyA,
8082
- onlyB = onlyB,
8083
- }
8084
- end
8085
6247
  return {
8086
6248
  getFileTree = getFileTree,
8087
6249
  searchFiles = searchFiles,
8088
6250
  getPlaceInfo = getPlaceInfo,
8089
- getServices = getServices,
8090
6251
  searchObjects = searchObjects,
8091
6252
  getInstanceProperties = getInstanceProperties,
8092
- getInstanceChildren = getInstanceChildren,
8093
6253
  searchByProperty = searchByProperty,
8094
6254
  getClassInfo = getClassInfo,
8095
6255
  getProjectStructure = getProjectStructure,
8096
6256
  grepScripts = grepScripts,
8097
- getDescendants = getDescendants,
8098
- compareInstances = compareInstances,
8099
6257
  }
8100
6258
  ]]></string>
8101
6259
  </Properties>
8102
6260
  </Item>
8103
- <Item class="ModuleScript" referent="21">
6261
+ <Item class="ModuleScript" referent="19">
8104
6262
  <Properties>
8105
6263
  <string name="Name">SceneAnalysisHandlers</string>
8106
6264
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -8349,18 +6507,18 @@ return {
8349
6507
  ]]></string>
8350
6508
  </Properties>
8351
6509
  </Item>
8352
- <Item class="ModuleScript" referent="22">
6510
+ <Item class="ModuleScript" referent="20">
8353
6511
  <Properties>
8354
6512
  <string name="Name">ScriptHandlers</string>
8355
6513
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
8356
6514
  local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
8357
6515
  local Utils = TS.import(script, script.Parent.Parent, "Utils")
8358
6516
  local Recording = TS.import(script, script.Parent.Parent, "Recording")
8359
- local ScriptEditorService = game:GetService("ScriptEditorService")
8360
6517
  local _binding = Utils
8361
6518
  local getInstancePath = _binding.getInstancePath
8362
6519
  local getInstanceByPath = _binding.getInstanceByPath
8363
6520
  local readScriptSource = _binding.readScriptSource
6521
+ local applyScriptSource = _binding.applyScriptSource
8364
6522
  local splitLines = _binding.splitLines
8365
6523
  local joinLines = _binding.joinLines
8366
6524
  local _binding_1 = Recording
@@ -8524,42 +6682,27 @@ local function setScriptSource(requestData)
8524
6682
  end
8525
6683
  local sourceToSet = normalizeEscapes(newSource)
8526
6684
  local recordingId = beginRecording(`Set script source: {instance.Name}`)
8527
- local updateSuccess, updateResult = pcall(function()
8528
- local oldSourceLength = #readScriptSource(instance)
8529
- ScriptEditorService:UpdateSourceAsync(instance, function()
8530
- return sourceToSet
8531
- end)
8532
- if readScriptSource(instance) ~= sourceToSet then
8533
- error("UpdateSourceAsync completed without updating the script source")
8534
- end
6685
+ local readSuccess, readResult = pcall(function()
6686
+ return #readScriptSource(instance)
6687
+ end)
6688
+ if not readSuccess then
6689
+ finishRecording(recordingId, false)
8535
6690
  return {
8536
- success = true,
8537
- instancePath = instancePath,
8538
- oldSourceLength = oldSourceLength,
8539
- newSourceLength = #sourceToSet,
8540
- method = "UpdateSourceAsync",
8541
- message = "Script source updated successfully (editor-safe)",
6691
+ error = `Failed to read script source before updating: {readResult}`,
8542
6692
  }
8543
- end)
8544
- if updateSuccess then
8545
- finishRecording(recordingId, true)
8546
- return updateResult
8547
6693
  end
8548
- local directSuccess, directResult = pcall(function()
8549
- local oldSource = instance.Source
8550
- instance.Source = sourceToSet
6694
+ local oldSourceLength = readResult
6695
+ local applyResult = applyScriptSource(instance, sourceToSet)
6696
+ if applyResult.success then
6697
+ finishRecording(recordingId, true)
8551
6698
  return {
8552
6699
  success = true,
8553
6700
  instancePath = instancePath,
8554
- oldSourceLength = #oldSource,
6701
+ oldSourceLength = oldSourceLength,
8555
6702
  newSourceLength = #sourceToSet,
8556
- method = "direct",
8557
- message = "Script source updated successfully (direct assignment)",
6703
+ method = applyResult.method,
6704
+ message = `Script source updated successfully ({if applyResult.method == "UpdateSourceAsync" then "editor-safe" else "direct assignment"})`,
8558
6705
  }
8559
- end)
8560
- if directSuccess then
8561
- finishRecording(recordingId, true)
8562
- return directResult
8563
6706
  end
8564
6707
  local replaceSuccess, replaceResult = pcall(function()
8565
6708
  local parent = instance.Parent
@@ -8569,9 +6712,15 @@ local function setScriptSource(requestData)
8569
6712
  local enabled = if wasBaseScript then instance.Enabled else nil
8570
6713
  local newScript = Instance.new(className)
8571
6714
  newScript.Name = name
8572
- newScript.Source = sourceToSet
6715
+ -- @rbxts/types does not expose PluginSecurity Source writes.
6716
+ local writableNewScript = newScript
6717
+ writableNewScript.Source = sourceToSet
6718
+ if readScriptSource(newScript) ~= sourceToSet then
6719
+ error("Replacement script source did not match the requested source")
6720
+ end
8573
6721
  if wasBaseScript and enabled ~= nil then
8574
- newScript.Enabled = enabled
6722
+ local newBaseScript = newScript
6723
+ newBaseScript.Enabled = enabled
8575
6724
  end
8576
6725
  newScript.Parent = parent
8577
6726
  instance:Destroy()
@@ -8588,7 +6737,7 @@ local function setScriptSource(requestData)
8588
6737
  end
8589
6738
  finishRecording(recordingId, false)
8590
6739
  return {
8591
- error = `Failed to set script source. UpdateSourceAsync failed: {updateResult}. Direct assignment failed: {directResult}. Replace method failed: {replaceResult}`,
6740
+ error = `Failed to set script source. {applyResult.error} Replace method failed: {replaceResult}`,
8592
6741
  }
8593
6742
  end
8594
6743
  local function editScriptLines(requestData)
@@ -8666,12 +6815,14 @@ local function editScriptLines(requestData)
8666
6815
  end
8667
6816
  -- Byte-slice replacement avoids Lua pattern escaping (safe for multi-byte chars like em dashes).
8668
6817
  local newSource = string.sub(source, 1, matchStart - 1) .. newString .. string.sub(source, matchStart + searchLen)
8669
- ScriptEditorService:UpdateSourceAsync(instance, function()
8670
- return newSource
8671
- end)
6818
+ local applyResult = applyScriptSource(instance, newSource, source)
6819
+ if not applyResult.success then
6820
+ error(applyResult.error)
6821
+ end
8672
6822
  return {
8673
6823
  success = true,
8674
6824
  instancePath = instancePath,
6825
+ method = applyResult.method,
8675
6826
  message = "Script edited successfully",
8676
6827
  }
8677
6828
  end)
@@ -8711,7 +6862,8 @@ local function insertScriptLines(requestData)
8711
6862
  end
8712
6863
  local recordingId = beginRecording(`Insert script lines after line {afterLine}: {instance.Name}`)
8713
6864
  local success, result = pcall(function()
8714
- local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
6865
+ local source = readScriptSource(instance)
6866
+ local lines, hadTrailingNewline = splitLines(source)
8715
6867
  local totalLines = #lines
8716
6868
  if afterLine < 0 or afterLine > totalLines then
8717
6869
  error(`afterLine out of range (0-{totalLines})`)
@@ -8754,15 +6906,17 @@ local function insertScriptLines(requestData)
8754
6906
  end
8755
6907
  end
8756
6908
  local newSource = joinLines(resultLines, hadTrailingNewline)
8757
- ScriptEditorService:UpdateSourceAsync(instance, function()
8758
- return newSource
8759
- end)
6909
+ local applyResult = applyScriptSource(instance, newSource, source)
6910
+ if not applyResult.success then
6911
+ error(applyResult.error)
6912
+ end
8760
6913
  return {
8761
6914
  success = true,
8762
6915
  instancePath = instancePath,
8763
6916
  insertedAfterLine = afterLine,
8764
6917
  linesInserted = #newLines,
8765
6918
  newLineCount = #resultLines,
6919
+ method = applyResult.method,
8766
6920
  message = "Script lines inserted successfully",
8767
6921
  }
8768
6922
  end)
@@ -8797,7 +6951,8 @@ local function deleteScriptLines(requestData)
8797
6951
  end
8798
6952
  local recordingId = beginRecording(`Delete script lines {startLine}-{endLine}: {instance.Name}`)
8799
6953
  local success, result = pcall(function()
8800
- local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
6954
+ local source = readScriptSource(instance)
6955
+ local lines, hadTrailingNewline = splitLines(source)
8801
6956
  local totalLines = #lines
8802
6957
  if startLine < 1 or startLine > totalLines then
8803
6958
  error(`startLine out of range (1-{totalLines})`)
@@ -8839,9 +6994,10 @@ local function deleteScriptLines(requestData)
8839
6994
  end
8840
6995
  end
8841
6996
  local newSource = joinLines(resultLines, hadTrailingNewline)
8842
- ScriptEditorService:UpdateSourceAsync(instance, function()
8843
- return newSource
8844
- end)
6997
+ local applyResult = applyScriptSource(instance, newSource, source)
6998
+ if not applyResult.success then
6999
+ error(applyResult.error)
7000
+ end
8845
7001
  return {
8846
7002
  success = true,
8847
7003
  instancePath = instancePath,
@@ -8851,6 +7007,7 @@ local function deleteScriptLines(requestData)
8851
7007
  },
8852
7008
  linesDeleted = endLine - startLine + 1,
8853
7009
  newLineCount = #resultLines,
7010
+ method = applyResult.method,
8854
7011
  message = "Script lines deleted successfully",
8855
7012
  }
8856
7013
  end)
@@ -8953,17 +7110,14 @@ local function findAndReplaceInScripts(requestData)
8953
7110
  if hitLimit then
8954
7111
  return nil
8955
7112
  end
8956
- if instance:IsA("LuaSourceContainer") then
8957
- local _condition_5 = classFilter
8958
- if _condition_5 ~= "" and _condition_5 then
8959
- local _exp = string.lower(instance.ClassName)
8960
- local _arg0 = string.lower(classFilter)
8961
- local _value = (string.find(_exp, _arg0))
8962
- _condition_5 = not (_value ~= 0 and _value == _value and _value)
8963
- end
8964
- if _condition_5 ~= "" and _condition_5 then
8965
- return nil
8966
- end
7113
+ local _condition_5 = classFilter == nil
7114
+ if not _condition_5 then
7115
+ local _exp = string.lower(instance.ClassName)
7116
+ local _arg0 = string.lower(classFilter)
7117
+ _condition_5 = (string.find(_exp, _arg0)) ~= nil
7118
+ end
7119
+ local matchesClass = _condition_5
7120
+ if instance:IsA("LuaSourceContainer") and matchesClass then
8967
7121
  scriptsSearched += 1
8968
7122
  local source = readScriptSource(instance)
8969
7123
  local newSource
@@ -8988,24 +7142,31 @@ local function findAndReplaceInScripts(requestData)
8988
7142
  hitLimit = true
8989
7143
  return nil
8990
7144
  end
8991
- totalReplacements += replCount
8992
- if not dryRun then
8993
- local ok = pcall(function()
8994
- ScriptEditorService:UpdateSourceAsync(instance, function()
8995
- return newSource
8996
- end)
8997
- end)
8998
- if not ok then
8999
- instance.Source = newSource
7145
+ local applyResult = if dryRun then nil else applyScriptSource(instance, newSource, source)
7146
+ if applyResult ~= nil and not applyResult.success then
7147
+ local _object = {
7148
+ instancePath = getInstancePath(instance),
7149
+ name = instance.Name,
7150
+ className = instance.ClassName,
7151
+ replacements = 0,
7152
+ }
7153
+ local _left = "error"
7154
+ local _condition_6 = applyResult.error
7155
+ if _condition_6 == nil then
7156
+ _condition_6 = "Script write failed verification"
9000
7157
  end
7158
+ _object[_left] = _condition_6
7159
+ table.insert(changes, _object)
7160
+ else
7161
+ totalReplacements += replCount
7162
+ local _arg0 = {
7163
+ instancePath = getInstancePath(instance),
7164
+ name = instance.Name,
7165
+ className = instance.ClassName,
7166
+ replacements = replCount,
7167
+ }
7168
+ table.insert(changes, _arg0)
9001
7169
  end
9002
- local _arg0 = {
9003
- instancePath = getInstancePath(instance),
9004
- name = instance.Name,
9005
- className = instance.ClassName,
9006
- replacements = replCount,
9007
- }
9008
- table.insert(changes, _arg0)
9009
7170
  end
9010
7171
  end
9011
7172
  for _, child in instance:GetChildren() do
@@ -9015,18 +7176,37 @@ local function findAndReplaceInScripts(requestData)
9015
7176
  processInstance(child)
9016
7177
  end
9017
7178
  end
9018
- processInstance(startInstance)
7179
+ local traversalSuccess, traversalResult = pcall(function()
7180
+ return processInstance(startInstance)
7181
+ end)
7182
+ -- ▼ ReadonlyArray.filter ▼
7183
+ local _newValue = {}
7184
+ local _callback = function(change)
7185
+ return change.error ~= nil
7186
+ end
7187
+ local _length = 0
7188
+ for _k, _v in changes do
7189
+ if _callback(_v, _k - 1, changes) == true then
7190
+ _length += 1
7191
+ _newValue[_length] = _v
7192
+ end
7193
+ end
7194
+ -- ▲ ReadonlyArray.filter ▲
7195
+ local failedScripts = #_newValue
7196
+ local scriptsModified = #changes - failedScripts
9019
7197
  if recordingId ~= nil then
9020
- finishRecording(recordingId, #changes > 0)
7198
+ finishRecording(recordingId, scriptsModified > 0)
9021
7199
  end
9022
7200
  return {
9023
- success = true,
7201
+ success = traversalSuccess and failedScripts == 0,
7202
+ error = if traversalSuccess then nil else `Script traversal failed: {traversalResult}`,
9024
7203
  dryRun = dryRun,
9025
7204
  pattern = searchPattern,
9026
7205
  replacement = replacement,
9027
7206
  totalReplacements = totalReplacements,
9028
7207
  scriptsSearched = scriptsSearched,
9029
- scriptsModified = #changes,
7208
+ scriptsModified = scriptsModified,
7209
+ scriptsFailed = failedScripts,
9030
7210
  changes = changes,
9031
7211
  truncated = hitLimit,
9032
7212
  }
@@ -9042,7 +7222,7 @@ return {
9042
7222
  ]]></string>
9043
7223
  </Properties>
9044
7224
  </Item>
9045
- <Item class="ModuleScript" referent="23">
7225
+ <Item class="ModuleScript" referent="21">
9046
7226
  <Properties>
9047
7227
  <string name="Name">ScriptProfilerHandlers</string>
9048
7228
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9505,7 +7685,7 @@ return {
9505
7685
  ]]></string>
9506
7686
  </Properties>
9507
7687
  </Item>
9508
- <Item class="ModuleScript" referent="24">
7688
+ <Item class="ModuleScript" referent="22">
9509
7689
  <Properties>
9510
7690
  <string name="Name">SerializationHandlers</string>
9511
7691
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9691,7 +7871,7 @@ return {
9691
7871
  ]]></string>
9692
7872
  </Properties>
9693
7873
  </Item>
9694
- <Item class="ModuleScript" referent="25">
7874
+ <Item class="ModuleScript" referent="23">
9695
7875
  <Properties>
9696
7876
  <string name="Name">TestHandlers</string>
9697
7877
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10036,13 +8216,25 @@ local function multiplayerTestLeaveClient(_requestData)
10036
8216
  localPlayer = localPlayer,
10037
8217
  }
10038
8218
  end
10039
- local function multiplayerTestEnd(_requestData)
8219
+ local function multiplayerTestEnd(requestData)
8220
+ if not RunService:IsRunning() or not RunService:IsServer() then
8221
+ return {
8222
+ error = "multiplayer_test_end must be called on the running server peer. Route with target=server.",
8223
+ }
8224
+ end
8225
+ local value = if requestData.value ~= nil then requestData.value else "ended_by_mcp"
8226
+ local ok, result = pcall(function()
8227
+ return StudioTestService:EndTest(value)
8228
+ end)
8229
+ if not ok then
8230
+ return {
8231
+ error = tostring(result),
8232
+ }
8233
+ end
10040
8234
  return {
10041
- success = false,
10042
- error = "multiplayer_stop_disabled",
10043
- message = "Multiplayer playtest stop/end is disabled because StudioTestService:EndTest is currently broken for this flow. Manually close the Studio multiplayer test windows instead.",
10044
- reason = "StudioTestService:EndTest does not reliably end StudioTestService multiplayer sessions from MCP right now.",
10045
- manualCleanupRequired = true,
8235
+ success = true,
8236
+ message = "Multiplayer Studio test end requested.",
8237
+ value = value,
10046
8238
  }
10047
8239
  end
10048
8240
  return {
@@ -10058,7 +8250,7 @@ return {
10058
8250
  </Properties>
10059
8251
  </Item>
10060
8252
  </Item>
10061
- <Item class="ModuleScript" referent="26">
8253
+ <Item class="ModuleScript" referent="24">
10062
8254
  <Properties>
10063
8255
  <string name="Name">HttpDiagnostics</string>
10064
8256
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10139,7 +8331,7 @@ return {
10139
8331
  ]]></string>
10140
8332
  </Properties>
10141
8333
  </Item>
10142
- <Item class="ModuleScript" referent="27">
8334
+ <Item class="ModuleScript" referent="25">
10143
8335
  <Properties>
10144
8336
  <string name="Name">LuauExec</string>
10145
8337
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10573,7 +8765,7 @@ return {
10573
8765
  ]]></string>
10574
8766
  </Properties>
10575
8767
  </Item>
10576
- <Item class="ModuleScript" referent="28">
8768
+ <Item class="ModuleScript" referent="26">
10577
8769
  <Properties>
10578
8770
  <string name="Name">Recording</string>
10579
8771
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10603,7 +8795,7 @@ return {
10603
8795
  ]]></string>
10604
8796
  </Properties>
10605
8797
  </Item>
10606
- <Item class="ModuleScript" referent="29">
8798
+ <Item class="ModuleScript" referent="27">
10607
8799
  <Properties>
10608
8800
  <string name="Name">RenderMonitor</string>
10609
8801
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10671,7 +8863,7 @@ return {
10671
8863
  ]]></string>
10672
8864
  </Properties>
10673
8865
  </Item>
10674
- <Item class="ModuleScript" referent="30">
8866
+ <Item class="ModuleScript" referent="28">
10675
8867
  <Properties>
10676
8868
  <string name="Name">RuntimeLogBuffer</string>
10677
8869
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10926,7 +9118,7 @@ return {
10926
9118
  ]]></string>
10927
9119
  </Properties>
10928
9120
  </Item>
10929
- <Item class="ModuleScript" referent="31">
9121
+ <Item class="ModuleScript" referent="29">
10930
9122
  <Properties>
10931
9123
  <string name="Name">ServerUrlSettings</string>
10932
9124
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -11076,11 +9268,11 @@ return {
11076
9268
  ]]></string>
11077
9269
  </Properties>
11078
9270
  </Item>
11079
- <Item class="ModuleScript" referent="32">
9271
+ <Item class="ModuleScript" referent="30">
11080
9272
  <Properties>
11081
9273
  <string name="Name">State</string>
11082
9274
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
11083
- local CURRENT_VERSION = "2.23.0"
9275
+ local CURRENT_VERSION = "3.0.0"
11084
9276
  local PLUGIN_VARIANT = "main"
11085
9277
  local BASE_PORT = 58741
11086
9278
  local function createConnection(port)
@@ -11116,7 +9308,7 @@ return {
11116
9308
  ]]></string>
11117
9309
  </Properties>
11118
9310
  </Item>
11119
- <Item class="ModuleScript" referent="33">
9311
+ <Item class="ModuleScript" referent="31">
11120
9312
  <Properties>
11121
9313
  <string name="Name">StopPlayMonitor</string>
11122
9314
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -11414,7 +9606,7 @@ return {
11414
9606
  ]]></string>
11415
9607
  </Properties>
11416
9608
  </Item>
11417
- <Item class="ModuleScript" referent="34">
9609
+ <Item class="ModuleScript" referent="32">
11418
9610
  <Properties>
11419
9611
  <string name="Name">UI</string>
11420
9612
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -11966,7 +10158,7 @@ return {
11966
10158
  ]]></string>
11967
10159
  </Properties>
11968
10160
  </Item>
11969
- <Item class="ModuleScript" referent="35">
10161
+ <Item class="ModuleScript" referent="33">
11970
10162
  <Properties>
11971
10163
  <string name="Name">Utils</string>
11972
10164
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -12286,11 +10478,62 @@ local function readScriptSource(instance)
12286
10478
  end
12287
10479
  return nil
12288
10480
  end)
12289
- local _value = ok and result
12290
- if _value ~= "" and _value then
10481
+ if ok and result ~= nil then
12291
10482
  return result
12292
10483
  end
12293
- return instance.Source
10484
+ -- @rbxts/types does not expose PluginSecurity Source reads.
10485
+ local readableScript = instance
10486
+ return readableScript.Source
10487
+ end
10488
+ --[[
10489
+ *
10490
+ * Writes newSource to instance and verifies the live editor text before
10491
+ * reporting success. expectedSource turns line-based edits into a
10492
+ * compare-and-set operation: if the document changes while
10493
+ * UpdateSourceAsync yields or before the direct fallback runs, the write is
10494
+ * rejected instead of overwriting the newer draft.
10495
+
10496
+ ]]
10497
+ local function applyScriptSource(instance, newSource, expectedSource)
10498
+ local updateSuccess, updateResult = pcall(function()
10499
+ ScriptEditorService:UpdateSourceAsync(instance, function(currentSource)
10500
+ if expectedSource ~= nil and currentSource ~= expectedSource then
10501
+ error("Script source changed while the edit was being applied; read the script again and retry")
10502
+ end
10503
+ return newSource
10504
+ end)
10505
+ if readScriptSource(instance) ~= newSource then
10506
+ error("UpdateSourceAsync completed without updating the live script source")
10507
+ end
10508
+ end)
10509
+ if updateSuccess then
10510
+ return {
10511
+ success = true,
10512
+ method = "UpdateSourceAsync",
10513
+ }
10514
+ end
10515
+ local directSuccess, directResult = pcall(function()
10516
+ if expectedSource ~= nil and readScriptSource(instance) ~= expectedSource then
10517
+ error("Script source changed before direct assignment; read the script again and retry")
10518
+ end
10519
+ -- @rbxts/types does not expose PluginSecurity Source writes.
10520
+ local writableScript = instance
10521
+ writableScript.Source = newSource
10522
+ if readScriptSource(instance) ~= newSource then
10523
+ error("Direct assignment completed without updating the live script source")
10524
+ end
10525
+ end)
10526
+ if directSuccess then
10527
+ return {
10528
+ success = true,
10529
+ method = "direct",
10530
+ }
10531
+ end
10532
+ return {
10533
+ success = false,
10534
+ method = "direct",
10535
+ error = `UpdateSourceAsync failed: {updateResult}. Direct assignment failed: {directResult}`,
10536
+ }
12294
10537
  end
12295
10538
  local function convertPropertyValue(instance, propertyName, propertyValue)
12296
10539
  if propertyValue == nil then
@@ -12751,6 +10994,7 @@ return {
12751
10994
  splitLines = splitLines,
12752
10995
  joinLines = joinLines,
12753
10996
  readScriptSource = readScriptSource,
10997
+ applyScriptSource = applyScriptSource,
12754
10998
  convertPropertyValue = convertPropertyValue,
12755
10999
  evaluateFormula = evaluateFormula,
12756
11000
  compareVersions = compareVersions,
@@ -12759,11 +11003,11 @@ return {
12759
11003
  </Properties>
12760
11004
  </Item>
12761
11005
  </Item>
12762
- <Item class="Folder" referent="40">
11006
+ <Item class="Folder" referent="38">
12763
11007
  <Properties>
12764
11008
  <string name="Name">include</string>
12765
11009
  </Properties>
12766
- <Item class="ModuleScript" referent="36">
11010
+ <Item class="ModuleScript" referent="34">
12767
11011
  <Properties>
12768
11012
  <string name="Name">LibMP</string>
12769
11013
  <string name="Source"><![CDATA[-- =============================================================================
@@ -169147,7 +167391,7 @@ return LibMP
169147
167391
  ]]></string>
169148
167392
  </Properties>
169149
167393
  </Item>
169150
- <Item class="ModuleScript" referent="37">
167394
+ <Item class="ModuleScript" referent="35">
169151
167395
  <Properties>
169152
167396
  <string name="Name">Promise</string>
169153
167397
  <string name="Source"><![CDATA[--[[
@@ -171221,7 +169465,7 @@ return Promise
171221
169465
  ]]></string>
171222
169466
  </Properties>
171223
169467
  </Item>
171224
- <Item class="ModuleScript" referent="38">
169468
+ <Item class="ModuleScript" referent="36">
171225
169469
  <Properties>
171226
169470
  <string name="Name">RuntimeLib</string>
171227
169471
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -171488,15 +169732,15 @@ return TS
171488
169732
  </Properties>
171489
169733
  </Item>
171490
169734
  </Item>
171491
- <Item class="Folder" referent="41">
169735
+ <Item class="Folder" referent="39">
171492
169736
  <Properties>
171493
169737
  <string name="Name">node_modules</string>
171494
169738
  </Properties>
171495
- <Item class="Folder" referent="42">
169739
+ <Item class="Folder" referent="40">
171496
169740
  <Properties>
171497
169741
  <string name="Name">@rbxts</string>
171498
169742
  </Properties>
171499
- <Item class="ModuleScript" referent="39">
169743
+ <Item class="ModuleScript" referent="37">
171500
169744
  <Properties>
171501
169745
  <string name="Name">services</string>
171502
169746
  <string name="Source"><![CDATA[return setmetatable({}, {