robloxstudio-mcp 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +173 -10
- package/dist/install-plugin-cli.js +87 -0
- package/package.json +3 -2
- package/studio-plugin/MCPPlugin.rbxmx +1522 -183
- package/studio-plugin/src/modules/Communication.ts +3 -0
- package/studio-plugin/src/modules/Utils.ts +1 -1
- package/studio-plugin/src/modules/handlers/AssetHandlers.ts +79 -68
- package/studio-plugin/src/modules/handlers/BuildHandlers.ts +36 -31
- package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +118 -0
- package/studio-plugin/src/modules/handlers/InstanceHandlers.ts +125 -72
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +25 -8
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +1 -0
|
@@ -52,6 +52,9 @@ local InstanceHandlers = TS.import(script, script.Parent, "handlers", "InstanceH
|
|
|
52
52
|
local ScriptHandlers = TS.import(script, script.Parent, "handlers", "ScriptHandlers")
|
|
53
53
|
local MetadataHandlers = TS.import(script, script.Parent, "handlers", "MetadataHandlers")
|
|
54
54
|
local TestHandlers = TS.import(script, script.Parent, "handlers", "TestHandlers")
|
|
55
|
+
local BuildHandlers = TS.import(script, script.Parent, "handlers", "BuildHandlers")
|
|
56
|
+
local AssetHandlers = TS.import(script, script.Parent, "handlers", "AssetHandlers")
|
|
57
|
+
local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
|
|
55
58
|
local routeMap = {
|
|
56
59
|
["/api/file-tree"] = QueryHandlers.getFileTree,
|
|
57
60
|
["/api/search-files"] = QueryHandlers.searchFiles,
|
|
@@ -63,6 +66,7 @@ local routeMap = {
|
|
|
63
66
|
["/api/search-by-property"] = QueryHandlers.searchByProperty,
|
|
64
67
|
["/api/class-info"] = QueryHandlers.getClassInfo,
|
|
65
68
|
["/api/project-structure"] = QueryHandlers.getProjectStructure,
|
|
69
|
+
["/api/grep-scripts"] = QueryHandlers.grepScripts,
|
|
66
70
|
["/api/set-property"] = PropertyHandlers.setProperty,
|
|
67
71
|
["/api/mass-set-property"] = PropertyHandlers.massSetProperty,
|
|
68
72
|
["/api/mass-get-property"] = PropertyHandlers.massGetProperty,
|
|
@@ -89,9 +93,18 @@ local routeMap = {
|
|
|
89
93
|
["/api/get-tagged"] = MetadataHandlers.getTagged,
|
|
90
94
|
["/api/get-selection"] = MetadataHandlers.getSelection,
|
|
91
95
|
["/api/execute-luau"] = MetadataHandlers.executeLuau,
|
|
96
|
+
["/api/undo"] = MetadataHandlers.undo,
|
|
97
|
+
["/api/redo"] = MetadataHandlers.redo,
|
|
92
98
|
["/api/start-playtest"] = TestHandlers.startPlaytest,
|
|
93
99
|
["/api/stop-playtest"] = TestHandlers.stopPlaytest,
|
|
94
100
|
["/api/get-playtest-output"] = TestHandlers.getPlaytestOutput,
|
|
101
|
+
["/api/export-build"] = BuildHandlers.exportBuild,
|
|
102
|
+
["/api/import-build"] = BuildHandlers.importBuild,
|
|
103
|
+
["/api/import-scene"] = BuildHandlers.importScene,
|
|
104
|
+
["/api/search-materials"] = BuildHandlers.searchMaterials,
|
|
105
|
+
["/api/insert-asset"] = AssetHandlers.insertAsset,
|
|
106
|
+
["/api/preview-asset"] = AssetHandlers.previewAsset,
|
|
107
|
+
["/api/capture-screenshot"] = CaptureHandlers.captureScreenshot,
|
|
95
108
|
}
|
|
96
109
|
local function processRequest(request)
|
|
97
110
|
local endpoint = request.endpoint
|
|
@@ -465,15 +478,1092 @@ return {
|
|
|
465
478
|
</Properties>
|
|
466
479
|
<Item class="ModuleScript" referent="4">
|
|
467
480
|
<Properties>
|
|
468
|
-
<string name="Name">
|
|
481
|
+
<string name="Name">AssetHandlers</string>
|
|
469
482
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
470
483
|
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
471
484
|
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
485
|
+
local Recording = TS.import(script, script.Parent.Parent, "Recording")
|
|
486
|
+
local AssetService = game:GetService("AssetService")
|
|
472
487
|
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
488
|
+
local Selection = game:GetService("Selection")
|
|
489
|
+
local _binding = Utils
|
|
490
|
+
local getInstancePath = _binding.getInstancePath
|
|
491
|
+
local getInstanceByPath = _binding.getInstanceByPath
|
|
492
|
+
local _binding_1 = Recording
|
|
493
|
+
local beginRecording = _binding_1.beginRecording
|
|
494
|
+
local finishRecording = _binding_1.finishRecording
|
|
495
|
+
local function insertAsset(requestData)
|
|
496
|
+
local assetId = requestData.assetId
|
|
497
|
+
local _condition = (requestData.parentPath)
|
|
498
|
+
if _condition == nil then
|
|
499
|
+
_condition = "game.Workspace"
|
|
500
|
+
end
|
|
501
|
+
local parentPath = _condition
|
|
502
|
+
local position = requestData.position
|
|
503
|
+
if not (assetId ~= 0 and assetId == assetId and assetId) then
|
|
504
|
+
return {
|
|
505
|
+
error = "assetId is required",
|
|
506
|
+
}
|
|
507
|
+
end
|
|
508
|
+
local parentInstance = getInstanceByPath(parentPath)
|
|
509
|
+
if not parentInstance then
|
|
510
|
+
return {
|
|
511
|
+
error = `Parent instance not found: {parentPath}`,
|
|
512
|
+
}
|
|
513
|
+
end
|
|
514
|
+
local recordingId = beginRecording(`Insert asset {assetId}`)
|
|
515
|
+
local wrapperModel
|
|
516
|
+
local insertSuccess, insertResult = pcall(function()
|
|
517
|
+
local loadedWrapper = AssetService:LoadAssetAsync(assetId)
|
|
518
|
+
wrapperModel = loadedWrapper
|
|
519
|
+
local insertedInstances = {}
|
|
520
|
+
local children = loadedWrapper:GetChildren()
|
|
521
|
+
for _, child in children do
|
|
522
|
+
child.Parent = parentInstance
|
|
523
|
+
table.insert(insertedInstances, child)
|
|
524
|
+
end
|
|
525
|
+
if position then
|
|
526
|
+
local _condition_1 = position.x
|
|
527
|
+
if _condition_1 == nil then
|
|
528
|
+
_condition_1 = 0
|
|
529
|
+
end
|
|
530
|
+
local _condition_2 = position.y
|
|
531
|
+
if _condition_2 == nil then
|
|
532
|
+
_condition_2 = 0
|
|
533
|
+
end
|
|
534
|
+
local _condition_3 = position.z
|
|
535
|
+
if _condition_3 == nil then
|
|
536
|
+
_condition_3 = 0
|
|
537
|
+
end
|
|
538
|
+
local pos = Vector3.new(_condition_1, _condition_2, _condition_3)
|
|
539
|
+
for _, inst in insertedInstances do
|
|
540
|
+
if inst:IsA("BasePart") then
|
|
541
|
+
inst.Position = pos
|
|
542
|
+
elseif inst:IsA("Model") then
|
|
543
|
+
if inst.PrimaryPart then
|
|
544
|
+
inst:PivotTo(CFrame.new(pos))
|
|
545
|
+
else
|
|
546
|
+
local firstPart = inst:FindFirstChildWhichIsA("BasePart", true)
|
|
547
|
+
if firstPart then
|
|
548
|
+
inst:PivotTo(CFrame.new(pos))
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
pcall(function()
|
|
555
|
+
Selection:Set(insertedInstances)
|
|
556
|
+
end)
|
|
557
|
+
-- ▼ ReadonlyArray.map ▼
|
|
558
|
+
local _newValue = table.create(#insertedInstances)
|
|
559
|
+
local _callback = function(inst)
|
|
560
|
+
return {
|
|
561
|
+
name = inst.Name,
|
|
562
|
+
className = inst.ClassName,
|
|
563
|
+
path = getInstancePath(inst),
|
|
564
|
+
}
|
|
565
|
+
end
|
|
566
|
+
for _k, _v in insertedInstances do
|
|
567
|
+
_newValue[_k] = _callback(_v, _k - 1, insertedInstances)
|
|
568
|
+
end
|
|
569
|
+
-- ▲ ReadonlyArray.map ▲
|
|
570
|
+
local resultInstances = _newValue
|
|
571
|
+
return {
|
|
572
|
+
success = true,
|
|
573
|
+
assetId = assetId,
|
|
574
|
+
parentPath = parentPath,
|
|
575
|
+
insertedCount = #insertedInstances,
|
|
576
|
+
instances = resultInstances,
|
|
577
|
+
}
|
|
578
|
+
end)
|
|
579
|
+
if wrapperModel then
|
|
580
|
+
pcall(function()
|
|
581
|
+
wrapperModel:Destroy()
|
|
582
|
+
end)
|
|
583
|
+
end
|
|
584
|
+
finishRecording(recordingId, insertSuccess)
|
|
585
|
+
if not insertSuccess then
|
|
586
|
+
return {
|
|
587
|
+
error = `Failed to insert asset {assetId}: {tostring(insertResult)}`,
|
|
588
|
+
}
|
|
589
|
+
end
|
|
590
|
+
return insertResult
|
|
591
|
+
end
|
|
592
|
+
local function previewAsset(requestData)
|
|
593
|
+
local assetId = requestData.assetId
|
|
594
|
+
local _condition = (requestData.includeProperties)
|
|
595
|
+
if _condition == nil then
|
|
596
|
+
_condition = true
|
|
597
|
+
end
|
|
598
|
+
local includeProperties = _condition
|
|
599
|
+
local _condition_1 = (requestData.maxDepth)
|
|
600
|
+
if _condition_1 == nil then
|
|
601
|
+
_condition_1 = 10
|
|
602
|
+
end
|
|
603
|
+
local maxDepth = _condition_1
|
|
604
|
+
if not (assetId ~= 0 and assetId == assetId and assetId) then
|
|
605
|
+
return {
|
|
606
|
+
error = "assetId is required",
|
|
607
|
+
}
|
|
608
|
+
end
|
|
609
|
+
local loadSuccess, wrapperModel = pcall(function()
|
|
610
|
+
return AssetService:LoadAssetAsync(assetId)
|
|
611
|
+
end)
|
|
612
|
+
if not loadSuccess or not wrapperModel then
|
|
613
|
+
return {
|
|
614
|
+
error = `Failed to load asset {assetId}: {tostring(wrapperModel)}`,
|
|
615
|
+
}
|
|
616
|
+
end
|
|
617
|
+
-- Stats tracking
|
|
618
|
+
local totalInstances = 0
|
|
619
|
+
local classCounts = {}
|
|
620
|
+
local hasScripts = false
|
|
621
|
+
local hasAnimations = false
|
|
622
|
+
local hasSounds = false
|
|
623
|
+
local hasParticles = false
|
|
624
|
+
local function buildHierarchy(instance, depth)
|
|
625
|
+
totalInstances += 1
|
|
626
|
+
local className = instance.ClassName
|
|
627
|
+
local _condition_2 = classCounts[className]
|
|
628
|
+
if _condition_2 == nil then
|
|
629
|
+
_condition_2 = 0
|
|
630
|
+
end
|
|
631
|
+
classCounts[className] = _condition_2 + 1
|
|
632
|
+
if instance:IsA("LuaSourceContainer") then
|
|
633
|
+
hasScripts = true
|
|
634
|
+
end
|
|
635
|
+
if className == "Animation" or className == "AnimationController" or className == "Animator" then
|
|
636
|
+
hasAnimations = true
|
|
637
|
+
end
|
|
638
|
+
if instance:IsA("Sound") then
|
|
639
|
+
hasSounds = true
|
|
640
|
+
end
|
|
641
|
+
if className == "ParticleEmitter" or className == "Fire" or className == "Smoke" or className == "Sparkles" then
|
|
642
|
+
hasParticles = true
|
|
643
|
+
end
|
|
644
|
+
local node = {
|
|
645
|
+
name = instance.Name,
|
|
646
|
+
className = className,
|
|
647
|
+
}
|
|
648
|
+
if includeProperties then
|
|
649
|
+
local props = {}
|
|
650
|
+
if instance:IsA("BasePart") then
|
|
651
|
+
props.size = {
|
|
652
|
+
x = instance.Size.X,
|
|
653
|
+
y = instance.Size.Y,
|
|
654
|
+
z = instance.Size.Z,
|
|
655
|
+
}
|
|
656
|
+
props.position = {
|
|
657
|
+
x = instance.Position.X,
|
|
658
|
+
y = instance.Position.Y,
|
|
659
|
+
z = instance.Position.Z,
|
|
660
|
+
}
|
|
661
|
+
props.material = tostring(instance.Material)
|
|
662
|
+
props.color = `{instance.Color.R}, {instance.Color.G}, {instance.Color.B}`
|
|
663
|
+
props.transparency = instance.Transparency
|
|
664
|
+
props.anchored = instance.Anchored
|
|
665
|
+
end
|
|
666
|
+
if instance:IsA("MeshPart") then
|
|
667
|
+
local meshPart = instance
|
|
668
|
+
props.meshId = meshPart.MeshId
|
|
669
|
+
props.textureId = meshPart.TextureID
|
|
670
|
+
end
|
|
671
|
+
if instance:IsA("Model") then
|
|
672
|
+
local model = instance
|
|
673
|
+
if model.PrimaryPart then
|
|
674
|
+
props.primaryPart = model.PrimaryPart.Name
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
if instance:IsA("LuaSourceContainer") then
|
|
678
|
+
local ok, src = pcall(function()
|
|
679
|
+
return instance.Source
|
|
680
|
+
end)
|
|
681
|
+
local _value = ok and src
|
|
682
|
+
if _value ~= "" and _value then
|
|
683
|
+
local preview = string.sub(src, 1, 200)
|
|
684
|
+
props.sourcePreview = preview
|
|
685
|
+
props.sourceLength = #src
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
if className == "Decal" or className == "Texture" then
|
|
689
|
+
local ok, texId = pcall(function()
|
|
690
|
+
return instance.Texture
|
|
691
|
+
end)
|
|
692
|
+
if ok then
|
|
693
|
+
props.texture = texId
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
if instance:IsA("Sound") then
|
|
697
|
+
props.soundId = instance.SoundId
|
|
698
|
+
end
|
|
699
|
+
-- Only include props if there are any
|
|
700
|
+
local hasProps = false
|
|
701
|
+
for _element, _element_1 in pairs(props) do
|
|
702
|
+
local _ = { _element, _element_1 }
|
|
703
|
+
hasProps = true
|
|
704
|
+
break
|
|
705
|
+
end
|
|
706
|
+
if hasProps then
|
|
707
|
+
node.properties = props
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
if depth < maxDepth then
|
|
711
|
+
local childNodes = {}
|
|
712
|
+
for _, child in instance:GetChildren() do
|
|
713
|
+
local _arg0 = buildHierarchy(child, depth + 1)
|
|
714
|
+
table.insert(childNodes, _arg0)
|
|
715
|
+
end
|
|
716
|
+
if #childNodes > 0 then
|
|
717
|
+
node.children = childNodes
|
|
718
|
+
end
|
|
719
|
+
else
|
|
720
|
+
local childCount = #instance:GetChildren()
|
|
721
|
+
if childCount > 0 then
|
|
722
|
+
node.childCount = childCount
|
|
723
|
+
node.truncated = true
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
return node
|
|
727
|
+
end
|
|
728
|
+
local previewSuccess, previewResult = pcall(function()
|
|
729
|
+
local hierarchyRoots = {}
|
|
730
|
+
for _, child in wrapperModel:GetChildren() do
|
|
731
|
+
local _arg0 = buildHierarchy(child, 0)
|
|
732
|
+
table.insert(hierarchyRoots, _arg0)
|
|
733
|
+
end
|
|
734
|
+
return {
|
|
735
|
+
success = true,
|
|
736
|
+
assetId = assetId,
|
|
737
|
+
hierarchy = hierarchyRoots,
|
|
738
|
+
summary = {
|
|
739
|
+
totalInstances = totalInstances,
|
|
740
|
+
classCounts = classCounts,
|
|
741
|
+
hasScripts = hasScripts,
|
|
742
|
+
hasAnimations = hasAnimations,
|
|
743
|
+
hasSounds = hasSounds,
|
|
744
|
+
hasParticles = hasParticles,
|
|
745
|
+
},
|
|
746
|
+
}
|
|
747
|
+
end)
|
|
748
|
+
pcall(function()
|
|
749
|
+
wrapperModel:Destroy()
|
|
750
|
+
end)
|
|
751
|
+
if not previewSuccess then
|
|
752
|
+
return {
|
|
753
|
+
error = `Failed to preview asset {assetId}: {tostring(previewResult)}`,
|
|
754
|
+
}
|
|
755
|
+
end
|
|
756
|
+
return previewResult
|
|
757
|
+
end
|
|
758
|
+
return {
|
|
759
|
+
insertAsset = insertAsset,
|
|
760
|
+
previewAsset = previewAsset,
|
|
761
|
+
}
|
|
762
|
+
]]></string>
|
|
763
|
+
</Properties>
|
|
764
|
+
</Item>
|
|
765
|
+
<Item class="ModuleScript" referent="5">
|
|
766
|
+
<Properties>
|
|
767
|
+
<string name="Name">BuildHandlers</string>
|
|
768
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
769
|
+
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
770
|
+
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
771
|
+
local Recording = TS.import(script, script.Parent.Parent, "Recording")
|
|
772
|
+
local MaterialService = game:GetService("MaterialService")
|
|
773
|
+
local _binding = Utils
|
|
774
|
+
local getInstancePath = _binding.getInstancePath
|
|
775
|
+
local getInstanceByPath = _binding.getInstanceByPath
|
|
776
|
+
local _binding_1 = Recording
|
|
777
|
+
local beginRecording = _binding_1.beginRecording
|
|
778
|
+
local finishRecording = _binding_1.finishRecording
|
|
779
|
+
local MATERIAL_BY_NAME = {}
|
|
780
|
+
for _, enumItem in Enum.Material:GetEnumItems() do
|
|
781
|
+
local _name = enumItem.Name
|
|
782
|
+
MATERIAL_BY_NAME[_name] = enumItem
|
|
783
|
+
end
|
|
784
|
+
-- Shape class mapping
|
|
785
|
+
local SHAPE_CLASSES = {
|
|
786
|
+
Block = "Part",
|
|
787
|
+
Wedge = "WedgePart",
|
|
788
|
+
Cylinder = "Part",
|
|
789
|
+
Ball = "Part",
|
|
790
|
+
CornerWedge = "CornerWedgePart",
|
|
791
|
+
}
|
|
792
|
+
local PALETTE_KEYS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
793
|
+
local function roundTo(n, decimals)
|
|
794
|
+
local mult = 10 ^ decimals
|
|
795
|
+
return math.round(n * mult) / mult
|
|
796
|
+
end
|
|
797
|
+
local function encodePaletteKey(index)
|
|
798
|
+
local base = #PALETTE_KEYS
|
|
799
|
+
local value = math.floor(index) + 1
|
|
800
|
+
local encoded = ""
|
|
801
|
+
while value > 0 do
|
|
802
|
+
value -= 1
|
|
803
|
+
local digit = value % base
|
|
804
|
+
local _arg0 = digit + 1
|
|
805
|
+
local _arg1 = digit + 1
|
|
806
|
+
encoded = string.sub(PALETTE_KEYS, _arg0, _arg1) .. encoded
|
|
807
|
+
value = math.floor(value / base)
|
|
808
|
+
end
|
|
809
|
+
return encoded
|
|
810
|
+
end
|
|
811
|
+
local function getVariantName(part)
|
|
812
|
+
local variantName = part.MaterialVariant
|
|
813
|
+
if variantName == "" then
|
|
814
|
+
local ok, variantAttr = pcall(function()
|
|
815
|
+
return part:GetAttribute("MaterialVariant")
|
|
816
|
+
end)
|
|
817
|
+
if ok and type(variantAttr) == "string" then
|
|
818
|
+
variantName = variantAttr
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
return variantName
|
|
822
|
+
end
|
|
823
|
+
local function exportBuild(requestData)
|
|
824
|
+
local instancePath = requestData.instancePath
|
|
825
|
+
local outputId = requestData.outputId
|
|
826
|
+
local _condition = (requestData.style)
|
|
827
|
+
if _condition == nil then
|
|
828
|
+
_condition = "misc"
|
|
829
|
+
end
|
|
830
|
+
local style = _condition
|
|
831
|
+
if not (instancePath ~= "" and instancePath) then
|
|
832
|
+
return {
|
|
833
|
+
error = "Instance path is required",
|
|
834
|
+
}
|
|
835
|
+
end
|
|
836
|
+
local instance = getInstanceByPath(instancePath)
|
|
837
|
+
if not instance then
|
|
838
|
+
return {
|
|
839
|
+
error = `Instance not found: {instancePath}`,
|
|
840
|
+
}
|
|
841
|
+
end
|
|
842
|
+
if not instance:IsA("Model") and not instance:IsA("Folder") then
|
|
843
|
+
return {
|
|
844
|
+
error = "Instance must be a Model or Folder",
|
|
845
|
+
}
|
|
846
|
+
end
|
|
847
|
+
local success, result = pcall(function()
|
|
848
|
+
local descendants = instance:GetDescendants()
|
|
849
|
+
local baseParts = {}
|
|
850
|
+
for _, desc in descendants do
|
|
851
|
+
if desc:IsA("BasePart") then
|
|
852
|
+
table.insert(baseParts, desc)
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
if #baseParts == 0 then
|
|
856
|
+
return {
|
|
857
|
+
error = "No BaseParts found in instance",
|
|
858
|
+
}
|
|
859
|
+
end
|
|
860
|
+
-- Compute bounding box center
|
|
861
|
+
local minX = math.huge
|
|
862
|
+
local minY = math.huge
|
|
863
|
+
local minZ = math.huge
|
|
864
|
+
local maxX = -math.huge
|
|
865
|
+
local maxY = -math.huge
|
|
866
|
+
local maxZ = -math.huge
|
|
867
|
+
for _, part in baseParts do
|
|
868
|
+
local pos = part.Position
|
|
869
|
+
local sz = part.Size
|
|
870
|
+
local halfX = sz.X / 2
|
|
871
|
+
local halfY = sz.Y / 2
|
|
872
|
+
local halfZ = sz.Z / 2
|
|
873
|
+
minX = math.min(minX, pos.X - halfX)
|
|
874
|
+
minY = math.min(minY, pos.Y - halfY)
|
|
875
|
+
minZ = math.min(minZ, pos.Z - halfZ)
|
|
876
|
+
maxX = math.max(maxX, pos.X + halfX)
|
|
877
|
+
maxY = math.max(maxY, pos.Y + halfY)
|
|
878
|
+
maxZ = math.max(maxZ, pos.Z + halfZ)
|
|
879
|
+
end
|
|
880
|
+
local centerX = (minX + maxX) / 2
|
|
881
|
+
local centerY = minY
|
|
882
|
+
local centerZ = (minZ + maxZ) / 2
|
|
883
|
+
local boundsX = roundTo(maxX - minX, 1)
|
|
884
|
+
local boundsY = roundTo(maxY - minY, 1)
|
|
885
|
+
local boundsZ = roundTo(maxZ - minZ, 1)
|
|
886
|
+
-- Build palette from unique (BrickColor, Material, MaterialVariant?) combos
|
|
887
|
+
local paletteMap = {}
|
|
888
|
+
local palette = {}
|
|
889
|
+
local keyIndex = 0
|
|
890
|
+
for _, part in baseParts do
|
|
891
|
+
local colorName = part.BrickColor.Name
|
|
892
|
+
local materialName = part.Material.Name
|
|
893
|
+
local variantName = getVariantName(part)
|
|
894
|
+
local combo = if variantName ~= "" then `{colorName}|{materialName}|{variantName}` else `{colorName}|{materialName}`
|
|
895
|
+
if not (paletteMap[combo] ~= nil) then
|
|
896
|
+
local key = encodePaletteKey(keyIndex)
|
|
897
|
+
keyIndex += 1
|
|
898
|
+
paletteMap[combo] = key
|
|
899
|
+
if variantName ~= "" then
|
|
900
|
+
palette[key] = { colorName, materialName, variantName }
|
|
901
|
+
else
|
|
902
|
+
palette[key] = { colorName, materialName }
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
-- Build compact part arrays
|
|
907
|
+
local parts = {}
|
|
908
|
+
for _, part in baseParts do
|
|
909
|
+
local pos = part.Position
|
|
910
|
+
local orient = part.Orientation
|
|
911
|
+
local sz = part.Size
|
|
912
|
+
local colorName = part.BrickColor.Name
|
|
913
|
+
local materialName = part.Material.Name
|
|
914
|
+
local variantName = getVariantName(part)
|
|
915
|
+
local combo = if variantName ~= "" then `{colorName}|{materialName}|{variantName}` else `{colorName}|{materialName}`
|
|
916
|
+
local _condition_1 = paletteMap[combo]
|
|
917
|
+
if _condition_1 == nil then
|
|
918
|
+
_condition_1 = "a"
|
|
919
|
+
end
|
|
920
|
+
local paletteKey = _condition_1
|
|
921
|
+
-- Relative position to center
|
|
922
|
+
local relX = roundTo(pos.X - centerX, 1)
|
|
923
|
+
local relY = roundTo(pos.Y - centerY, 1)
|
|
924
|
+
local relZ = roundTo(pos.Z - centerZ, 1)
|
|
925
|
+
local sizeX = roundTo(sz.X, 1)
|
|
926
|
+
local sizeY = roundTo(sz.Y, 1)
|
|
927
|
+
local sizeZ = roundTo(sz.Z, 1)
|
|
928
|
+
local rotX = roundTo(orient.X, 1)
|
|
929
|
+
local rotY = roundTo(orient.Y, 1)
|
|
930
|
+
local rotZ = roundTo(orient.Z, 1)
|
|
931
|
+
-- Determine shape
|
|
932
|
+
local shape = "Block"
|
|
933
|
+
if part:IsA("WedgePart") then
|
|
934
|
+
shape = "Wedge"
|
|
935
|
+
elseif part:IsA("CornerWedgePart") then
|
|
936
|
+
shape = "CornerWedge"
|
|
937
|
+
elseif part:IsA("Part") then
|
|
938
|
+
local p = part
|
|
939
|
+
if p.Shape == Enum.PartType.Cylinder then
|
|
940
|
+
shape = "Cylinder"
|
|
941
|
+
elseif p.Shape == Enum.PartType.Ball then
|
|
942
|
+
shape = "Ball"
|
|
943
|
+
end
|
|
944
|
+
end
|
|
945
|
+
-- Build part array with optional trailing fields
|
|
946
|
+
local hasTransparency = part.Transparency > 0
|
|
947
|
+
local hasShape = shape ~= "Block"
|
|
948
|
+
local partArr
|
|
949
|
+
if hasTransparency then
|
|
950
|
+
partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey, if hasShape then shape else "Block", roundTo(part.Transparency, 2) }
|
|
951
|
+
elseif hasShape then
|
|
952
|
+
partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey, shape }
|
|
953
|
+
else
|
|
954
|
+
partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey }
|
|
955
|
+
end
|
|
956
|
+
local _partArr = partArr
|
|
957
|
+
table.insert(parts, _partArr)
|
|
958
|
+
end
|
|
959
|
+
local _condition_1 = outputId
|
|
960
|
+
if _condition_1 == nil then
|
|
961
|
+
_condition_1 = `{style}/{(string.gsub(string.lower(instance.Name), " ", "_"))}`
|
|
962
|
+
end
|
|
963
|
+
local buildId = _condition_1
|
|
964
|
+
return {
|
|
965
|
+
success = true,
|
|
966
|
+
buildData = {
|
|
967
|
+
id = buildId,
|
|
968
|
+
style = style,
|
|
969
|
+
bounds = { boundsX, boundsY, boundsZ },
|
|
970
|
+
palette = palette,
|
|
971
|
+
parts = parts,
|
|
972
|
+
},
|
|
973
|
+
}
|
|
974
|
+
end)
|
|
975
|
+
if success and result then
|
|
976
|
+
return result
|
|
977
|
+
else
|
|
978
|
+
return {
|
|
979
|
+
error = `Failed to export build: {result}`,
|
|
980
|
+
}
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
local function importBuild(requestData)
|
|
984
|
+
local buildData = requestData.buildData
|
|
985
|
+
local targetPath = requestData.targetPath
|
|
986
|
+
local positionOffset = (requestData.position) or { 0, 0, 0 }
|
|
987
|
+
if not buildData or not (targetPath ~= "" and targetPath) then
|
|
988
|
+
return {
|
|
989
|
+
error = "buildData and targetPath are required",
|
|
990
|
+
}
|
|
991
|
+
end
|
|
992
|
+
local parentInstance = getInstanceByPath(targetPath)
|
|
993
|
+
if not parentInstance then
|
|
994
|
+
return {
|
|
995
|
+
error = `Target not found: {targetPath}`,
|
|
996
|
+
}
|
|
997
|
+
end
|
|
998
|
+
local recordingId = beginRecording("Import build")
|
|
999
|
+
local success, result = pcall(function()
|
|
1000
|
+
local palette = buildData.palette
|
|
1001
|
+
local parts = buildData.parts
|
|
1002
|
+
local _condition = (buildData.id)
|
|
1003
|
+
if _condition == nil then
|
|
1004
|
+
_condition = "imported_build"
|
|
1005
|
+
end
|
|
1006
|
+
local buildId = _condition
|
|
1007
|
+
-- Create model container
|
|
1008
|
+
local model = Instance.new("Model")
|
|
1009
|
+
local _condition_1 = (string.match(buildId, "[^/]+$"))
|
|
1010
|
+
if _condition_1 == nil then
|
|
1011
|
+
_condition_1 = buildId
|
|
1012
|
+
end
|
|
1013
|
+
model.Name = _condition_1
|
|
1014
|
+
local partCount = 0
|
|
1015
|
+
for _, partArr in parts do
|
|
1016
|
+
local _exp = (partArr[1])
|
|
1017
|
+
local _condition_2 = positionOffset[1]
|
|
1018
|
+
if _condition_2 == nil then
|
|
1019
|
+
_condition_2 = 0
|
|
1020
|
+
end
|
|
1021
|
+
local posX = _exp + _condition_2
|
|
1022
|
+
local _exp_1 = (partArr[2])
|
|
1023
|
+
local _condition_3 = positionOffset[2]
|
|
1024
|
+
if _condition_3 == nil then
|
|
1025
|
+
_condition_3 = 0
|
|
1026
|
+
end
|
|
1027
|
+
local posY = _exp_1 + _condition_3
|
|
1028
|
+
local _exp_2 = (partArr[3])
|
|
1029
|
+
local _condition_4 = positionOffset[3]
|
|
1030
|
+
if _condition_4 == nil then
|
|
1031
|
+
_condition_4 = 0
|
|
1032
|
+
end
|
|
1033
|
+
local posZ = _exp_2 + _condition_4
|
|
1034
|
+
local sizeX = partArr[4]
|
|
1035
|
+
local sizeY = partArr[5]
|
|
1036
|
+
local sizeZ = partArr[6]
|
|
1037
|
+
local rotX = partArr[7]
|
|
1038
|
+
local rotY = partArr[8]
|
|
1039
|
+
local rotZ = partArr[9]
|
|
1040
|
+
local paletteKey = partArr[10]
|
|
1041
|
+
local _condition_5 = (partArr[11])
|
|
1042
|
+
if _condition_5 == nil then
|
|
1043
|
+
_condition_5 = "Block"
|
|
1044
|
+
end
|
|
1045
|
+
local shape = _condition_5
|
|
1046
|
+
local _condition_6 = (partArr[12])
|
|
1047
|
+
if _condition_6 == nil then
|
|
1048
|
+
_condition_6 = 0
|
|
1049
|
+
end
|
|
1050
|
+
local transparency = _condition_6
|
|
1051
|
+
-- Determine class from shape
|
|
1052
|
+
local _condition_7 = SHAPE_CLASSES[shape]
|
|
1053
|
+
if _condition_7 == nil then
|
|
1054
|
+
_condition_7 = "Part"
|
|
1055
|
+
end
|
|
1056
|
+
local className = _condition_7
|
|
1057
|
+
local part = Instance.new(className)
|
|
1058
|
+
-- Set shape for Part instances with non-Block shapes
|
|
1059
|
+
if className == "Part" and shape ~= "Block" then
|
|
1060
|
+
if shape == "Cylinder" then
|
|
1061
|
+
part.Shape = Enum.PartType.Cylinder
|
|
1062
|
+
elseif shape == "Ball" then
|
|
1063
|
+
part.Shape = Enum.PartType.Ball
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
1066
|
+
part.Size = Vector3.new(sizeX, sizeY, sizeZ)
|
|
1067
|
+
part.Position = Vector3.new(posX, posY, posZ)
|
|
1068
|
+
part.Orientation = Vector3.new(rotX, rotY, rotZ)
|
|
1069
|
+
part.Anchored = true
|
|
1070
|
+
if transparency > 0 then
|
|
1071
|
+
part.Transparency = transparency
|
|
1072
|
+
end
|
|
1073
|
+
-- Apply palette
|
|
1074
|
+
local paletteEntry = palette[paletteKey]
|
|
1075
|
+
if paletteEntry then
|
|
1076
|
+
local _binding_2 = paletteEntry
|
|
1077
|
+
local colorName = _binding_2[1]
|
|
1078
|
+
local materialName = _binding_2[2]
|
|
1079
|
+
local variantName = _binding_2[3]
|
|
1080
|
+
pcall(function()
|
|
1081
|
+
part.BrickColor = BrickColor.new(colorName)
|
|
1082
|
+
end)
|
|
1083
|
+
pcall(function()
|
|
1084
|
+
local mat = MATERIAL_BY_NAME[materialName]
|
|
1085
|
+
if mat ~= nil then
|
|
1086
|
+
part.Material = mat
|
|
1087
|
+
end
|
|
1088
|
+
end)
|
|
1089
|
+
-- Apply MaterialVariant if specified
|
|
1090
|
+
if variantName ~= nil and variantName ~= "" then
|
|
1091
|
+
pcall(function()
|
|
1092
|
+
part.MaterialVariant = variantName
|
|
1093
|
+
end)
|
|
1094
|
+
end
|
|
1095
|
+
end
|
|
1096
|
+
part.Parent = model
|
|
1097
|
+
partCount += 1
|
|
1098
|
+
end
|
|
1099
|
+
model.Parent = parentInstance
|
|
1100
|
+
return {
|
|
1101
|
+
success = true,
|
|
1102
|
+
partCount = partCount,
|
|
1103
|
+
modelPath = getInstancePath(model),
|
|
1104
|
+
}
|
|
1105
|
+
end)
|
|
1106
|
+
if success and result then
|
|
1107
|
+
finishRecording(recordingId, true)
|
|
1108
|
+
return result
|
|
1109
|
+
else
|
|
1110
|
+
finishRecording(recordingId, false)
|
|
1111
|
+
return {
|
|
1112
|
+
error = `Failed to import build: {result}`,
|
|
1113
|
+
}
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
local function importScene(requestData)
|
|
1117
|
+
local expandedBuilds = requestData.expandedBuilds
|
|
1118
|
+
local _condition = (requestData.targetPath)
|
|
1119
|
+
if _condition == nil then
|
|
1120
|
+
_condition = "game.Workspace"
|
|
1121
|
+
end
|
|
1122
|
+
local targetPath = _condition
|
|
1123
|
+
if not expandedBuilds or not (type(expandedBuilds) == "table") or #expandedBuilds == 0 then
|
|
1124
|
+
return {
|
|
1125
|
+
error = "expandedBuilds array is required",
|
|
1126
|
+
}
|
|
1127
|
+
end
|
|
1128
|
+
local parentInstance = getInstanceByPath(targetPath)
|
|
1129
|
+
if not parentInstance then
|
|
1130
|
+
return {
|
|
1131
|
+
error = `Target not found: {targetPath}`,
|
|
1132
|
+
}
|
|
1133
|
+
end
|
|
1134
|
+
local recordingId = beginRecording("Import scene")
|
|
1135
|
+
local success, result = pcall(function()
|
|
1136
|
+
local modelCount = 0
|
|
1137
|
+
local totalParts = 0
|
|
1138
|
+
local models = {}
|
|
1139
|
+
for _, entry in expandedBuilds do
|
|
1140
|
+
local buildData = entry.buildData
|
|
1141
|
+
local position = (entry.position) or { 0, 0, 0 }
|
|
1142
|
+
local rotation = (entry.rotation) or { 0, 0, 0 }
|
|
1143
|
+
local _condition_1 = (entry.name)
|
|
1144
|
+
if _condition_1 == nil then
|
|
1145
|
+
_condition_1 = "SceneModel"
|
|
1146
|
+
end
|
|
1147
|
+
local name = _condition_1
|
|
1148
|
+
local palette = buildData.palette
|
|
1149
|
+
local parts = buildData.parts
|
|
1150
|
+
local model = Instance.new("Model")
|
|
1151
|
+
model.Name = name
|
|
1152
|
+
local _condition_2 = rotation[1]
|
|
1153
|
+
if _condition_2 == nil then
|
|
1154
|
+
_condition_2 = 0
|
|
1155
|
+
end
|
|
1156
|
+
local _exp = math.rad(_condition_2)
|
|
1157
|
+
local _condition_3 = rotation[2]
|
|
1158
|
+
if _condition_3 == nil then
|
|
1159
|
+
_condition_3 = 0
|
|
1160
|
+
end
|
|
1161
|
+
local _exp_1 = math.rad(_condition_3)
|
|
1162
|
+
local _condition_4 = rotation[3]
|
|
1163
|
+
if _condition_4 == nil then
|
|
1164
|
+
_condition_4 = 0
|
|
1165
|
+
end
|
|
1166
|
+
local rotCF = CFrame.Angles(_exp, _exp_1, math.rad(_condition_4))
|
|
1167
|
+
local _condition_5 = position[1]
|
|
1168
|
+
if _condition_5 == nil then
|
|
1169
|
+
_condition_5 = 0
|
|
1170
|
+
end
|
|
1171
|
+
local _condition_6 = position[2]
|
|
1172
|
+
if _condition_6 == nil then
|
|
1173
|
+
_condition_6 = 0
|
|
1174
|
+
end
|
|
1175
|
+
local _condition_7 = position[3]
|
|
1176
|
+
if _condition_7 == nil then
|
|
1177
|
+
_condition_7 = 0
|
|
1178
|
+
end
|
|
1179
|
+
local originCF = CFrame.new(_condition_5, _condition_6, _condition_7) * rotCF
|
|
1180
|
+
local partCount = 0
|
|
1181
|
+
for _1, partArr in parts do
|
|
1182
|
+
local localX = partArr[1]
|
|
1183
|
+
local localY = partArr[2]
|
|
1184
|
+
local localZ = partArr[3]
|
|
1185
|
+
local sizeX = partArr[4]
|
|
1186
|
+
local sizeY = partArr[5]
|
|
1187
|
+
local sizeZ = partArr[6]
|
|
1188
|
+
local rotX = partArr[7]
|
|
1189
|
+
local rotY = partArr[8]
|
|
1190
|
+
local rotZ = partArr[9]
|
|
1191
|
+
local paletteKey = partArr[10]
|
|
1192
|
+
local _condition_8 = (partArr[11])
|
|
1193
|
+
if _condition_8 == nil then
|
|
1194
|
+
_condition_8 = "Block"
|
|
1195
|
+
end
|
|
1196
|
+
local shape = _condition_8
|
|
1197
|
+
local _condition_9 = (partArr[12])
|
|
1198
|
+
if _condition_9 == nil then
|
|
1199
|
+
_condition_9 = 0
|
|
1200
|
+
end
|
|
1201
|
+
local transparency = _condition_9
|
|
1202
|
+
local _condition_10 = SHAPE_CLASSES[shape]
|
|
1203
|
+
if _condition_10 == nil then
|
|
1204
|
+
_condition_10 = "Part"
|
|
1205
|
+
end
|
|
1206
|
+
local className = _condition_10
|
|
1207
|
+
local part = Instance.new(className)
|
|
1208
|
+
if className == "Part" and shape ~= "Block" then
|
|
1209
|
+
if shape == "Cylinder" then
|
|
1210
|
+
part.Shape = Enum.PartType.Cylinder
|
|
1211
|
+
elseif shape == "Ball" then
|
|
1212
|
+
part.Shape = Enum.PartType.Ball
|
|
1213
|
+
end
|
|
1214
|
+
end
|
|
1215
|
+
part.Size = Vector3.new(sizeX, sizeY, sizeZ)
|
|
1216
|
+
-- Apply local rotation then world transform
|
|
1217
|
+
local localRotCF = CFrame.Angles(math.rad(rotX), math.rad(rotY), math.rad(rotZ))
|
|
1218
|
+
local localPosCF = CFrame.new(localX, localY, localZ) * localRotCF
|
|
1219
|
+
local worldCF = originCF * localPosCF
|
|
1220
|
+
part.CFrame = worldCF
|
|
1221
|
+
part.Anchored = true
|
|
1222
|
+
if transparency > 0 then
|
|
1223
|
+
part.Transparency = transparency
|
|
1224
|
+
end
|
|
1225
|
+
local paletteEntry = palette[paletteKey]
|
|
1226
|
+
if paletteEntry then
|
|
1227
|
+
local _binding_2 = paletteEntry
|
|
1228
|
+
local colorName = _binding_2[1]
|
|
1229
|
+
local materialName = _binding_2[2]
|
|
1230
|
+
local variantName = _binding_2[3]
|
|
1231
|
+
pcall(function()
|
|
1232
|
+
part.BrickColor = BrickColor.new(colorName)
|
|
1233
|
+
end)
|
|
1234
|
+
pcall(function()
|
|
1235
|
+
local mat = MATERIAL_BY_NAME[materialName]
|
|
1236
|
+
if mat ~= nil then
|
|
1237
|
+
part.Material = mat
|
|
1238
|
+
end
|
|
1239
|
+
end)
|
|
1240
|
+
if variantName ~= nil and variantName ~= "" then
|
|
1241
|
+
pcall(function()
|
|
1242
|
+
part.MaterialVariant = variantName
|
|
1243
|
+
end)
|
|
1244
|
+
end
|
|
1245
|
+
end
|
|
1246
|
+
part.Parent = model
|
|
1247
|
+
partCount += 1
|
|
1248
|
+
end
|
|
1249
|
+
model.Parent = parentInstance
|
|
1250
|
+
modelCount += 1
|
|
1251
|
+
totalParts += partCount
|
|
1252
|
+
local _arg0 = {
|
|
1253
|
+
name = name,
|
|
1254
|
+
partCount = partCount,
|
|
1255
|
+
modelPath = getInstancePath(model),
|
|
1256
|
+
}
|
|
1257
|
+
table.insert(models, _arg0)
|
|
1258
|
+
end
|
|
1259
|
+
return {
|
|
1260
|
+
success = true,
|
|
1261
|
+
modelCount = modelCount,
|
|
1262
|
+
totalParts = totalParts,
|
|
1263
|
+
models = models,
|
|
1264
|
+
}
|
|
1265
|
+
end)
|
|
1266
|
+
if success and result then
|
|
1267
|
+
finishRecording(recordingId, true)
|
|
1268
|
+
return result
|
|
1269
|
+
else
|
|
1270
|
+
finishRecording(recordingId, false)
|
|
1271
|
+
return {
|
|
1272
|
+
error = `Failed to import scene: {result}`,
|
|
1273
|
+
}
|
|
1274
|
+
end
|
|
1275
|
+
end
|
|
1276
|
+
local function searchMaterials(requestData)
|
|
1277
|
+
local _condition = (requestData.query)
|
|
1278
|
+
if _condition == nil then
|
|
1279
|
+
_condition = ""
|
|
1280
|
+
end
|
|
1281
|
+
local query = string.lower(_condition)
|
|
1282
|
+
local _condition_1 = (requestData.maxResults)
|
|
1283
|
+
if _condition_1 == nil then
|
|
1284
|
+
_condition_1 = 50
|
|
1285
|
+
end
|
|
1286
|
+
local maxResults = _condition_1
|
|
1287
|
+
local success, result = pcall(function()
|
|
1288
|
+
local children = MaterialService:GetChildren()
|
|
1289
|
+
local materials = {}
|
|
1290
|
+
for _, child in children do
|
|
1291
|
+
if not child:IsA("MaterialVariant") then
|
|
1292
|
+
continue
|
|
1293
|
+
end
|
|
1294
|
+
local nameMatch = query == "" or (string.find(string.lower(child.Name), query)) ~= nil
|
|
1295
|
+
if not nameMatch then
|
|
1296
|
+
continue
|
|
1297
|
+
end
|
|
1298
|
+
local _arg0 = {
|
|
1299
|
+
name = child.Name,
|
|
1300
|
+
baseMaterial = child.BaseMaterial.Name,
|
|
1301
|
+
}
|
|
1302
|
+
table.insert(materials, _arg0)
|
|
1303
|
+
if #materials >= maxResults then
|
|
1304
|
+
break
|
|
1305
|
+
end
|
|
1306
|
+
end
|
|
1307
|
+
return {
|
|
1308
|
+
success = true,
|
|
1309
|
+
materials = materials,
|
|
1310
|
+
total = #materials,
|
|
1311
|
+
}
|
|
1312
|
+
end)
|
|
1313
|
+
if success and result then
|
|
1314
|
+
return result
|
|
1315
|
+
else
|
|
1316
|
+
return {
|
|
1317
|
+
error = `Failed to search materials: {result}`,
|
|
1318
|
+
}
|
|
1319
|
+
end
|
|
1320
|
+
end
|
|
1321
|
+
return {
|
|
1322
|
+
exportBuild = exportBuild,
|
|
1323
|
+
importBuild = importBuild,
|
|
1324
|
+
importScene = importScene,
|
|
1325
|
+
searchMaterials = searchMaterials,
|
|
1326
|
+
}
|
|
1327
|
+
]]></string>
|
|
1328
|
+
</Properties>
|
|
1329
|
+
</Item>
|
|
1330
|
+
<Item class="ModuleScript" referent="6">
|
|
1331
|
+
<Properties>
|
|
1332
|
+
<string name="Name">CaptureHandlers</string>
|
|
1333
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
1334
|
+
local CaptureService = game:GetService("CaptureService")
|
|
1335
|
+
local AssetService = game:GetService("AssetService")
|
|
1336
|
+
local MAX_TILE_SIZE = 1024
|
|
1337
|
+
local BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
1338
|
+
local function encodeBase64(buf)
|
|
1339
|
+
local len = buffer.len(buf)
|
|
1340
|
+
local parts = {}
|
|
1341
|
+
local i = 0
|
|
1342
|
+
while i + 2 < len do
|
|
1343
|
+
local b0 = buffer.readu8(buf, i)
|
|
1344
|
+
local b1 = buffer.readu8(buf, i + 1)
|
|
1345
|
+
local b2 = buffer.readu8(buf, i + 2)
|
|
1346
|
+
local triplet = bit32.lshift(b0, 16) + bit32.lshift(b1, 8) + b2
|
|
1347
|
+
local _arg0 = string.sub(BASE64_CHARS, bit32.rshift(triplet, 18) + 1, bit32.rshift(triplet, 18) + 1) .. string.sub(BASE64_CHARS, bit32.band(bit32.rshift(triplet, 12), 63) + 1, bit32.band(bit32.rshift(triplet, 12), 63) + 1) .. string.sub(BASE64_CHARS, bit32.band(bit32.rshift(triplet, 6), 63) + 1, bit32.band(bit32.rshift(triplet, 6), 63) + 1) .. string.sub(BASE64_CHARS, bit32.band(triplet, 63) + 1, bit32.band(triplet, 63) + 1)
|
|
1348
|
+
table.insert(parts, _arg0)
|
|
1349
|
+
i += 3
|
|
1350
|
+
end
|
|
1351
|
+
local remaining = len - i
|
|
1352
|
+
if remaining == 2 then
|
|
1353
|
+
local b0 = buffer.readu8(buf, i)
|
|
1354
|
+
local b1 = buffer.readu8(buf, i + 1)
|
|
1355
|
+
local triplet = bit32.lshift(b0, 16) + bit32.lshift(b1, 8)
|
|
1356
|
+
local _arg0 = string.sub(BASE64_CHARS, bit32.rshift(triplet, 18) + 1, bit32.rshift(triplet, 18) + 1) .. string.sub(BASE64_CHARS, bit32.band(bit32.rshift(triplet, 12), 63) + 1, bit32.band(bit32.rshift(triplet, 12), 63) + 1) .. string.sub(BASE64_CHARS, bit32.band(bit32.rshift(triplet, 6), 63) + 1, bit32.band(bit32.rshift(triplet, 6), 63) + 1) .. "="
|
|
1357
|
+
table.insert(parts, _arg0)
|
|
1358
|
+
elseif remaining == 1 then
|
|
1359
|
+
local b0 = buffer.readu8(buf, i)
|
|
1360
|
+
local triplet = bit32.lshift(b0, 16)
|
|
1361
|
+
local _arg0 = string.sub(BASE64_CHARS, bit32.rshift(triplet, 18) + 1, bit32.rshift(triplet, 18) + 1) .. string.sub(BASE64_CHARS, bit32.band(bit32.rshift(triplet, 12), 63) + 1, bit32.band(bit32.rshift(triplet, 12), 63) + 1) .. "=="
|
|
1362
|
+
table.insert(parts, _arg0)
|
|
1363
|
+
end
|
|
1364
|
+
return table.concat(parts, "")
|
|
1365
|
+
end
|
|
1366
|
+
local function readPixelsTiled(img, w, h)
|
|
1367
|
+
local BYTES_PER_PIXEL = 4
|
|
1368
|
+
local fullBuf = buffer.create(w * h * BYTES_PER_PIXEL)
|
|
1369
|
+
local fullRowBytes = w * BYTES_PER_PIXEL
|
|
1370
|
+
do
|
|
1371
|
+
local ty = 0
|
|
1372
|
+
local _shouldIncrement = false
|
|
1373
|
+
while true do
|
|
1374
|
+
if _shouldIncrement then
|
|
1375
|
+
ty += MAX_TILE_SIZE
|
|
1376
|
+
else
|
|
1377
|
+
_shouldIncrement = true
|
|
1378
|
+
end
|
|
1379
|
+
if not (ty < h) then
|
|
1380
|
+
break
|
|
1381
|
+
end
|
|
1382
|
+
local tileH = math.min(MAX_TILE_SIZE, h - ty)
|
|
1383
|
+
do
|
|
1384
|
+
local tx = 0
|
|
1385
|
+
local _shouldIncrement_1 = false
|
|
1386
|
+
while true do
|
|
1387
|
+
if _shouldIncrement_1 then
|
|
1388
|
+
tx += MAX_TILE_SIZE
|
|
1389
|
+
else
|
|
1390
|
+
_shouldIncrement_1 = true
|
|
1391
|
+
end
|
|
1392
|
+
if not (tx < w) then
|
|
1393
|
+
break
|
|
1394
|
+
end
|
|
1395
|
+
local tileW = math.min(MAX_TILE_SIZE, w - tx)
|
|
1396
|
+
local tileBuf = img:ReadPixelsBuffer(Vector2.new(tx, ty), Vector2.new(tileW, tileH))
|
|
1397
|
+
local tileRowBytes = tileW * BYTES_PER_PIXEL
|
|
1398
|
+
do
|
|
1399
|
+
local row = 0
|
|
1400
|
+
local _shouldIncrement_2 = false
|
|
1401
|
+
while true do
|
|
1402
|
+
if _shouldIncrement_2 then
|
|
1403
|
+
row += 1
|
|
1404
|
+
else
|
|
1405
|
+
_shouldIncrement_2 = true
|
|
1406
|
+
end
|
|
1407
|
+
if not (row < tileH) then
|
|
1408
|
+
break
|
|
1409
|
+
end
|
|
1410
|
+
buffer.copy(fullBuf, (ty + row) * fullRowBytes + tx * BYTES_PER_PIXEL, tileBuf, row * tileRowBytes, tileRowBytes)
|
|
1411
|
+
end
|
|
1412
|
+
end
|
|
1413
|
+
end
|
|
1414
|
+
end
|
|
1415
|
+
end
|
|
1416
|
+
end
|
|
1417
|
+
return fullBuf
|
|
1418
|
+
end
|
|
1419
|
+
local function captureScreenshot()
|
|
1420
|
+
local contentId
|
|
1421
|
+
CaptureService:CaptureScreenshot(function(id)
|
|
1422
|
+
contentId = id
|
|
1423
|
+
end)
|
|
1424
|
+
local startTime = tick()
|
|
1425
|
+
while contentId == nil do
|
|
1426
|
+
if tick() - startTime > 10 then
|
|
1427
|
+
return {
|
|
1428
|
+
error = "Screenshot capture timed out. Ensure the Studio viewport is visible and you are in Edit mode (not Play mode). Known Roblox bug: capture may fail if viewport renders a solid color.",
|
|
1429
|
+
}
|
|
1430
|
+
end
|
|
1431
|
+
task.wait(0.1)
|
|
1432
|
+
end
|
|
1433
|
+
local editableOk, editableResult = pcall(function()
|
|
1434
|
+
return AssetService:CreateEditableImageAsync(Content.fromUri(contentId))
|
|
1435
|
+
end)
|
|
1436
|
+
if not editableOk then
|
|
1437
|
+
return {
|
|
1438
|
+
error = `Failed to create EditableImage from screenshot. Enable EditableImage API: Game Settings > Security > 'Allow Mesh / Image APIs'. ({tostring(editableResult)})`,
|
|
1439
|
+
}
|
|
1440
|
+
end
|
|
1441
|
+
local editableImage = editableResult
|
|
1442
|
+
local imgSize = editableImage.Size
|
|
1443
|
+
local w = math.floor(imgSize.X)
|
|
1444
|
+
local h = math.floor(imgSize.Y)
|
|
1445
|
+
local readOk, pixelBuffer = pcall(function()
|
|
1446
|
+
return readPixelsTiled(editableImage, w, h)
|
|
1447
|
+
end)
|
|
1448
|
+
editableImage:Destroy()
|
|
1449
|
+
if not readOk then
|
|
1450
|
+
return {
|
|
1451
|
+
error = `Failed to read pixel data: {tostring(pixelBuffer)}`,
|
|
1452
|
+
}
|
|
1453
|
+
end
|
|
1454
|
+
local base64Data = encodeBase64(pixelBuffer)
|
|
1455
|
+
return {
|
|
1456
|
+
success = true,
|
|
1457
|
+
width = w,
|
|
1458
|
+
height = h,
|
|
1459
|
+
data = base64Data,
|
|
1460
|
+
}
|
|
1461
|
+
end
|
|
1462
|
+
return {
|
|
1463
|
+
captureScreenshot = captureScreenshot,
|
|
1464
|
+
}
|
|
1465
|
+
]]></string>
|
|
1466
|
+
</Properties>
|
|
1467
|
+
</Item>
|
|
1468
|
+
<Item class="ModuleScript" referent="7">
|
|
1469
|
+
<Properties>
|
|
1470
|
+
<string name="Name">InstanceHandlers</string>
|
|
1471
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
1472
|
+
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
1473
|
+
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
1474
|
+
local Recording = TS.import(script, script.Parent.Parent, "Recording")
|
|
473
1475
|
local _binding = Utils
|
|
474
1476
|
local getInstancePath = _binding.getInstancePath
|
|
475
1477
|
local getInstanceByPath = _binding.getInstanceByPath
|
|
476
1478
|
local convertPropertyValue = _binding.convertPropertyValue
|
|
1479
|
+
local _binding_1 = Recording
|
|
1480
|
+
local beginRecording = _binding_1.beginRecording
|
|
1481
|
+
local finishRecording = _binding_1.finishRecording
|
|
1482
|
+
local function processObjectEntries(objects, createFn)
|
|
1483
|
+
local results = {}
|
|
1484
|
+
local successCount = 0
|
|
1485
|
+
local failureCount = 0
|
|
1486
|
+
local loopSuccess, loopError = pcall(function()
|
|
1487
|
+
for _, entry in objects do
|
|
1488
|
+
if not (type(entry) == "table") then
|
|
1489
|
+
failureCount += 1
|
|
1490
|
+
table.insert(results, {
|
|
1491
|
+
success = false,
|
|
1492
|
+
error = "Each object entry must be a table",
|
|
1493
|
+
})
|
|
1494
|
+
continue
|
|
1495
|
+
end
|
|
1496
|
+
local objData = entry
|
|
1497
|
+
local className = objData.className
|
|
1498
|
+
local parentPath = objData.parent
|
|
1499
|
+
if not (className ~= "" and className) or not (parentPath ~= "" and parentPath) then
|
|
1500
|
+
failureCount += 1
|
|
1501
|
+
table.insert(results, {
|
|
1502
|
+
success = false,
|
|
1503
|
+
error = "Class name and parent are required",
|
|
1504
|
+
})
|
|
1505
|
+
continue
|
|
1506
|
+
end
|
|
1507
|
+
local entrySuccess, entryResult = pcall(function()
|
|
1508
|
+
return createFn(objData)
|
|
1509
|
+
end)
|
|
1510
|
+
if not entrySuccess then
|
|
1511
|
+
failureCount += 1
|
|
1512
|
+
local _arg0 = {
|
|
1513
|
+
success = false,
|
|
1514
|
+
className = className,
|
|
1515
|
+
parent = parentPath,
|
|
1516
|
+
error = tostring(entryResult),
|
|
1517
|
+
}
|
|
1518
|
+
table.insert(results, _arg0)
|
|
1519
|
+
continue
|
|
1520
|
+
end
|
|
1521
|
+
if entryResult.instance ~= nil then
|
|
1522
|
+
successCount += 1
|
|
1523
|
+
local _arg0 = {
|
|
1524
|
+
success = true,
|
|
1525
|
+
className = entryResult.className,
|
|
1526
|
+
parent = entryResult.parentPath,
|
|
1527
|
+
instancePath = getInstancePath(entryResult.instance),
|
|
1528
|
+
name = entryResult.instance.Name,
|
|
1529
|
+
}
|
|
1530
|
+
table.insert(results, _arg0)
|
|
1531
|
+
else
|
|
1532
|
+
failureCount += 1
|
|
1533
|
+
local _object = {
|
|
1534
|
+
success = false,
|
|
1535
|
+
}
|
|
1536
|
+
local _left = "className"
|
|
1537
|
+
local _condition = entryResult.className
|
|
1538
|
+
if _condition == nil then
|
|
1539
|
+
_condition = className
|
|
1540
|
+
end
|
|
1541
|
+
_object[_left] = _condition
|
|
1542
|
+
local _left_1 = "parent"
|
|
1543
|
+
local _condition_1 = entryResult.parentPath
|
|
1544
|
+
if _condition_1 == nil then
|
|
1545
|
+
_condition_1 = parentPath
|
|
1546
|
+
end
|
|
1547
|
+
_object[_left_1] = _condition_1
|
|
1548
|
+
_object.error = entryResult.error
|
|
1549
|
+
table.insert(results, _object)
|
|
1550
|
+
end
|
|
1551
|
+
end
|
|
1552
|
+
end)
|
|
1553
|
+
if not loopSuccess then
|
|
1554
|
+
failureCount += 1
|
|
1555
|
+
local _arg0 = {
|
|
1556
|
+
success = false,
|
|
1557
|
+
error = `Unexpected mass create failure: {tostring(loopError)}`,
|
|
1558
|
+
}
|
|
1559
|
+
table.insert(results, _arg0)
|
|
1560
|
+
end
|
|
1561
|
+
return {
|
|
1562
|
+
results = results,
|
|
1563
|
+
successCount = successCount,
|
|
1564
|
+
failureCount = failureCount,
|
|
1565
|
+
}
|
|
1566
|
+
end
|
|
477
1567
|
local function createObject(requestData)
|
|
478
1568
|
local className = requestData.className
|
|
479
1569
|
local parentPath = requestData.parent
|
|
@@ -490,6 +1580,7 @@ local function createObject(requestData)
|
|
|
490
1580
|
error = `Parent instance not found: {parentPath}`,
|
|
491
1581
|
}
|
|
492
1582
|
end
|
|
1583
|
+
local recordingId = beginRecording(`Create {className}`)
|
|
493
1584
|
local success, newInstance = pcall(function()
|
|
494
1585
|
local instance = Instance.new(className)
|
|
495
1586
|
if name ~= "" and name then
|
|
@@ -501,10 +1592,10 @@ local function createObject(requestData)
|
|
|
501
1592
|
end)
|
|
502
1593
|
end
|
|
503
1594
|
instance.Parent = parentInstance
|
|
504
|
-
ChangeHistoryService:SetWaypoint(`Create {className}`)
|
|
505
1595
|
return instance
|
|
506
1596
|
end)
|
|
507
1597
|
if success and newInstance then
|
|
1598
|
+
finishRecording(recordingId, true)
|
|
508
1599
|
return {
|
|
509
1600
|
success = true,
|
|
510
1601
|
className = className,
|
|
@@ -514,6 +1605,7 @@ local function createObject(requestData)
|
|
|
514
1605
|
message = "Object created successfully",
|
|
515
1606
|
}
|
|
516
1607
|
else
|
|
1608
|
+
finishRecording(recordingId, false)
|
|
517
1609
|
return {
|
|
518
1610
|
error = `Failed to create object: {newInstance}`,
|
|
519
1611
|
className = className,
|
|
@@ -539,20 +1631,20 @@ local function deleteObject(requestData)
|
|
|
539
1631
|
error = "Cannot delete the game instance",
|
|
540
1632
|
}
|
|
541
1633
|
end
|
|
1634
|
+
local recordingId = beginRecording(`Delete {instance.ClassName} ({instance.Name})`)
|
|
542
1635
|
local success, result = pcall(function()
|
|
543
|
-
local name = instance.Name
|
|
544
|
-
local className = instance.ClassName
|
|
545
1636
|
instance:Destroy()
|
|
546
|
-
ChangeHistoryService:SetWaypoint(`Delete {className} ({name})`)
|
|
547
1637
|
return true
|
|
548
1638
|
end)
|
|
549
1639
|
if success then
|
|
1640
|
+
finishRecording(recordingId, true)
|
|
550
1641
|
return {
|
|
551
1642
|
success = true,
|
|
552
1643
|
instancePath = instancePath,
|
|
553
1644
|
message = "Object deleted successfully",
|
|
554
1645
|
}
|
|
555
1646
|
else
|
|
1647
|
+
finishRecording(recordingId, false)
|
|
556
1648
|
return {
|
|
557
1649
|
error = `Failed to delete object: {result}`,
|
|
558
1650
|
instancePath = instancePath,
|
|
@@ -566,69 +1658,44 @@ local function massCreateObjects(requestData)
|
|
|
566
1658
|
error = "Objects array is required",
|
|
567
1659
|
}
|
|
568
1660
|
end
|
|
569
|
-
local
|
|
570
|
-
local
|
|
571
|
-
local failureCount = 0
|
|
572
|
-
for _, objData in objects do
|
|
1661
|
+
local recordingId = beginRecording("Mass create objects")
|
|
1662
|
+
local _binding_2 = processObjectEntries(objects, function(objData)
|
|
573
1663
|
local className = objData.className
|
|
574
1664
|
local parentPath = objData.parent
|
|
575
1665
|
local name = objData.name
|
|
576
|
-
local
|
|
577
|
-
if
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
instance.Parent = parentInstance
|
|
589
|
-
return instance
|
|
590
|
-
end)
|
|
591
|
-
if success and newInstance then
|
|
592
|
-
successCount += 1
|
|
593
|
-
local _arg0 = {
|
|
594
|
-
success = true,
|
|
595
|
-
className = className,
|
|
596
|
-
parent = parentPath,
|
|
597
|
-
instancePath = getInstancePath(newInstance),
|
|
598
|
-
name = newInstance.Name,
|
|
599
|
-
}
|
|
600
|
-
table.insert(results, _arg0)
|
|
601
|
-
else
|
|
602
|
-
failureCount += 1
|
|
603
|
-
local _arg0 = {
|
|
604
|
-
success = false,
|
|
605
|
-
className = className,
|
|
606
|
-
parent = parentPath,
|
|
607
|
-
error = tostring(newInstance),
|
|
608
|
-
}
|
|
609
|
-
table.insert(results, _arg0)
|
|
610
|
-
end
|
|
611
|
-
else
|
|
612
|
-
failureCount += 1
|
|
613
|
-
local _arg0 = {
|
|
614
|
-
success = false,
|
|
615
|
-
className = className,
|
|
616
|
-
parent = parentPath,
|
|
617
|
-
error = "Parent instance not found",
|
|
618
|
-
}
|
|
619
|
-
table.insert(results, _arg0)
|
|
1666
|
+
local parentInstance = getInstanceByPath(parentPath)
|
|
1667
|
+
if not parentInstance then
|
|
1668
|
+
return {
|
|
1669
|
+
error = "Parent instance not found",
|
|
1670
|
+
className = className,
|
|
1671
|
+
parentPath = parentPath,
|
|
1672
|
+
}
|
|
1673
|
+
end
|
|
1674
|
+
local success, newInstance = pcall(function()
|
|
1675
|
+
local instance = Instance.new(className)
|
|
1676
|
+
if name ~= "" and name then
|
|
1677
|
+
instance.Name = name
|
|
620
1678
|
end
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1679
|
+
instance.Parent = parentInstance
|
|
1680
|
+
return instance
|
|
1681
|
+
end)
|
|
1682
|
+
if not success or not newInstance then
|
|
1683
|
+
return {
|
|
1684
|
+
error = tostring(newInstance),
|
|
1685
|
+
className = className,
|
|
1686
|
+
parentPath = parentPath,
|
|
1687
|
+
}
|
|
627
1688
|
end
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1689
|
+
return {
|
|
1690
|
+
instance = newInstance,
|
|
1691
|
+
className = className,
|
|
1692
|
+
parentPath = parentPath,
|
|
1693
|
+
}
|
|
1694
|
+
end)
|
|
1695
|
+
local results = _binding_2.results
|
|
1696
|
+
local successCount = _binding_2.successCount
|
|
1697
|
+
local failureCount = _binding_2.failureCount
|
|
1698
|
+
finishRecording(recordingId, successCount > 0)
|
|
632
1699
|
return {
|
|
633
1700
|
results = results,
|
|
634
1701
|
summary = {
|
|
@@ -645,78 +1712,55 @@ local function massCreateObjectsWithProperties(requestData)
|
|
|
645
1712
|
error = "Objects array is required",
|
|
646
1713
|
}
|
|
647
1714
|
end
|
|
648
|
-
local
|
|
649
|
-
local
|
|
650
|
-
local failureCount = 0
|
|
651
|
-
for _, objData in objects do
|
|
1715
|
+
local recordingId = beginRecording("Mass create objects with properties")
|
|
1716
|
+
local _binding_2 = processObjectEntries(objects, function(objData)
|
|
652
1717
|
local className = objData.className
|
|
653
1718
|
local parentPath = objData.parent
|
|
654
1719
|
local name = objData.name
|
|
655
|
-
local
|
|
656
|
-
local
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1720
|
+
local propertiesRaw = objData.properties
|
|
1721
|
+
local properties = if type(propertiesRaw) == "table" then propertiesRaw else ({})
|
|
1722
|
+
local parentInstance = getInstanceByPath(parentPath)
|
|
1723
|
+
if not parentInstance then
|
|
1724
|
+
return {
|
|
1725
|
+
error = "Parent instance not found",
|
|
1726
|
+
className = className,
|
|
1727
|
+
parentPath = parentPath,
|
|
1728
|
+
}
|
|
1729
|
+
end
|
|
1730
|
+
local success, newInstance = pcall(function()
|
|
1731
|
+
local instance = Instance.new(className)
|
|
1732
|
+
if name ~= "" and name then
|
|
1733
|
+
instance.Name = name
|
|
1734
|
+
end
|
|
1735
|
+
instance.Parent = parentInstance
|
|
1736
|
+
for propName, propValue in pairs(properties) do
|
|
1737
|
+
pcall(function()
|
|
1738
|
+
local propNameStr = tostring(propName)
|
|
1739
|
+
local converted = convertPropertyValue(instance, propNameStr, propValue)
|
|
1740
|
+
if converted ~= nil then
|
|
1741
|
+
instance[propNameStr] = converted
|
|
676
1742
|
end
|
|
677
|
-
return instance
|
|
678
1743
|
end)
|
|
679
|
-
if success and newInstance then
|
|
680
|
-
successCount += 1
|
|
681
|
-
local _arg0 = {
|
|
682
|
-
success = true,
|
|
683
|
-
className = className,
|
|
684
|
-
parent = parentPath,
|
|
685
|
-
instancePath = getInstancePath(newInstance),
|
|
686
|
-
name = newInstance.Name,
|
|
687
|
-
}
|
|
688
|
-
table.insert(results, _arg0)
|
|
689
|
-
else
|
|
690
|
-
failureCount += 1
|
|
691
|
-
local _arg0 = {
|
|
692
|
-
success = false,
|
|
693
|
-
className = className,
|
|
694
|
-
parent = parentPath,
|
|
695
|
-
error = tostring(newInstance),
|
|
696
|
-
}
|
|
697
|
-
table.insert(results, _arg0)
|
|
698
|
-
end
|
|
699
|
-
else
|
|
700
|
-
failureCount += 1
|
|
701
|
-
local _arg0 = {
|
|
702
|
-
success = false,
|
|
703
|
-
className = className,
|
|
704
|
-
parent = parentPath,
|
|
705
|
-
error = "Parent instance not found",
|
|
706
|
-
}
|
|
707
|
-
table.insert(results, _arg0)
|
|
708
1744
|
end
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
error =
|
|
714
|
-
|
|
1745
|
+
return instance
|
|
1746
|
+
end)
|
|
1747
|
+
if not success or not newInstance then
|
|
1748
|
+
return {
|
|
1749
|
+
error = tostring(newInstance),
|
|
1750
|
+
className = className,
|
|
1751
|
+
parentPath = parentPath,
|
|
1752
|
+
}
|
|
715
1753
|
end
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
1754
|
+
return {
|
|
1755
|
+
instance = newInstance,
|
|
1756
|
+
className = className,
|
|
1757
|
+
parentPath = parentPath,
|
|
1758
|
+
}
|
|
1759
|
+
end)
|
|
1760
|
+
local results = _binding_2.results
|
|
1761
|
+
local successCount = _binding_2.successCount
|
|
1762
|
+
local failureCount = _binding_2.failureCount
|
|
1763
|
+
finishRecording(recordingId, successCount > 0)
|
|
720
1764
|
return {
|
|
721
1765
|
results = results,
|
|
722
1766
|
summary = {
|
|
@@ -726,7 +1770,10 @@ local function massCreateObjectsWithProperties(requestData)
|
|
|
726
1770
|
},
|
|
727
1771
|
}
|
|
728
1772
|
end
|
|
729
|
-
local function
|
|
1773
|
+
local function performSmartDuplicate(requestData, useRecording)
|
|
1774
|
+
if useRecording == nil then
|
|
1775
|
+
useRecording = true
|
|
1776
|
+
end
|
|
730
1777
|
local instancePath = requestData.instancePath
|
|
731
1778
|
local count = requestData.count
|
|
732
1779
|
local options = (requestData.options) or {}
|
|
@@ -741,6 +1788,7 @@ local function smartDuplicate(requestData)
|
|
|
741
1788
|
error = `Instance not found: {instancePath}`,
|
|
742
1789
|
}
|
|
743
1790
|
end
|
|
1791
|
+
local recordingId = if useRecording then beginRecording(`Smart duplicate {instance.Name}`) else nil
|
|
744
1792
|
local results = {}
|
|
745
1793
|
local successCount = 0
|
|
746
1794
|
local failureCount = 0
|
|
@@ -885,9 +1933,7 @@ local function smartDuplicate(requestData)
|
|
|
885
1933
|
_i = i
|
|
886
1934
|
end
|
|
887
1935
|
end
|
|
888
|
-
|
|
889
|
-
ChangeHistoryService:SetWaypoint(`Smart duplicate {instance.Name} ({successCount} copies)`)
|
|
890
|
-
end
|
|
1936
|
+
finishRecording(recordingId, successCount > 0)
|
|
891
1937
|
return {
|
|
892
1938
|
results = results,
|
|
893
1939
|
summary = {
|
|
@@ -898,6 +1944,9 @@ local function smartDuplicate(requestData)
|
|
|
898
1944
|
sourceInstance = instancePath,
|
|
899
1945
|
}
|
|
900
1946
|
end
|
|
1947
|
+
local function smartDuplicate(requestData)
|
|
1948
|
+
return performSmartDuplicate(requestData, true)
|
|
1949
|
+
end
|
|
901
1950
|
local function massDuplicate(requestData)
|
|
902
1951
|
local duplications = requestData.duplications
|
|
903
1952
|
if not duplications or not (type(duplications) == "table") or #duplications == 0 then
|
|
@@ -908,17 +1957,16 @@ local function massDuplicate(requestData)
|
|
|
908
1957
|
local allResults = {}
|
|
909
1958
|
local totalSuccess = 0
|
|
910
1959
|
local totalFailures = 0
|
|
1960
|
+
local recordingId = beginRecording("Mass duplicate operations")
|
|
911
1961
|
for _, duplication in duplications do
|
|
912
|
-
local result =
|
|
1962
|
+
local result = performSmartDuplicate(duplication, false)
|
|
913
1963
|
table.insert(allResults, result)
|
|
914
1964
|
if result.summary then
|
|
915
1965
|
totalSuccess += result.summary.succeeded
|
|
916
1966
|
totalFailures += result.summary.failed
|
|
917
1967
|
end
|
|
918
1968
|
end
|
|
919
|
-
|
|
920
|
-
ChangeHistoryService:SetWaypoint(`Mass duplicate operations ({totalSuccess} objects)`)
|
|
921
|
-
end
|
|
1969
|
+
finishRecording(recordingId, totalSuccess > 0)
|
|
922
1970
|
return {
|
|
923
1971
|
results = allResults,
|
|
924
1972
|
summary = {
|
|
@@ -939,18 +1987,22 @@ return {
|
|
|
939
1987
|
]]></string>
|
|
940
1988
|
</Properties>
|
|
941
1989
|
</Item>
|
|
942
|
-
<Item class="ModuleScript" referent="
|
|
1990
|
+
<Item class="ModuleScript" referent="8">
|
|
943
1991
|
<Properties>
|
|
944
1992
|
<string name="Name">MetadataHandlers</string>
|
|
945
1993
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
946
1994
|
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
947
1995
|
local CollectionService = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services").CollectionService
|
|
948
1996
|
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
1997
|
+
local Recording = TS.import(script, script.Parent.Parent, "Recording")
|
|
949
1998
|
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
950
1999
|
local Selection = game:GetService("Selection")
|
|
951
2000
|
local _binding = Utils
|
|
952
2001
|
local getInstancePath = _binding.getInstancePath
|
|
953
2002
|
local getInstanceByPath = _binding.getInstanceByPath
|
|
2003
|
+
local _binding_1 = Recording
|
|
2004
|
+
local beginRecording = _binding_1.beginRecording
|
|
2005
|
+
local finishRecording = _binding_1.finishRecording
|
|
954
2006
|
local function serializeValue(value)
|
|
955
2007
|
local _value = value
|
|
956
2008
|
local vType = typeof(_value)
|
|
@@ -1133,10 +2185,10 @@ local function setAttribute(requestData)
|
|
|
1133
2185
|
error = `Instance not found: {instancePath}`,
|
|
1134
2186
|
}
|
|
1135
2187
|
end
|
|
2188
|
+
local recordingId = beginRecording(`Set attribute {attributeName} on {instance.Name}`)
|
|
1136
2189
|
local success, result = pcall(function()
|
|
1137
2190
|
local value = deserializeValue(attributeValue, valueType)
|
|
1138
2191
|
instance:SetAttribute(attributeName, value)
|
|
1139
|
-
ChangeHistoryService:SetWaypoint(`Set attribute {attributeName} on {instance.Name}`)
|
|
1140
2192
|
return {
|
|
1141
2193
|
success = true,
|
|
1142
2194
|
instancePath = instancePath,
|
|
@@ -1146,8 +2198,10 @@ local function setAttribute(requestData)
|
|
|
1146
2198
|
}
|
|
1147
2199
|
end)
|
|
1148
2200
|
if success then
|
|
2201
|
+
finishRecording(recordingId, true)
|
|
1149
2202
|
return result
|
|
1150
2203
|
end
|
|
2204
|
+
finishRecording(recordingId, false)
|
|
1151
2205
|
return {
|
|
1152
2206
|
error = `Failed to set attribute: {result}`,
|
|
1153
2207
|
}
|
|
@@ -1203,10 +2257,10 @@ local function deleteAttribute(requestData)
|
|
|
1203
2257
|
error = `Instance not found: {instancePath}`,
|
|
1204
2258
|
}
|
|
1205
2259
|
end
|
|
2260
|
+
local recordingId = beginRecording(`Delete attribute {attributeName} from {instance.Name}`)
|
|
1206
2261
|
local success, result = pcall(function()
|
|
1207
2262
|
local existed = instance:GetAttribute(attributeName) ~= nil
|
|
1208
2263
|
instance:SetAttribute(attributeName, nil)
|
|
1209
|
-
ChangeHistoryService:SetWaypoint(`Delete attribute {attributeName} from {instance.Name}`)
|
|
1210
2264
|
return {
|
|
1211
2265
|
success = true,
|
|
1212
2266
|
instancePath = instancePath,
|
|
@@ -1216,8 +2270,10 @@ local function deleteAttribute(requestData)
|
|
|
1216
2270
|
}
|
|
1217
2271
|
end)
|
|
1218
2272
|
if success then
|
|
2273
|
+
finishRecording(recordingId, true)
|
|
1219
2274
|
return result
|
|
1220
2275
|
end
|
|
2276
|
+
finishRecording(recordingId, false)
|
|
1221
2277
|
return {
|
|
1222
2278
|
error = `Failed to delete attribute: {result}`,
|
|
1223
2279
|
}
|
|
@@ -1264,10 +2320,10 @@ local function addTag(requestData)
|
|
|
1264
2320
|
error = `Instance not found: {instancePath}`,
|
|
1265
2321
|
}
|
|
1266
2322
|
end
|
|
2323
|
+
local recordingId = beginRecording(`Add tag {tagName} to {instance.Name}`)
|
|
1267
2324
|
local success, result = pcall(function()
|
|
1268
2325
|
local alreadyHad = CollectionService:HasTag(instance, tagName)
|
|
1269
2326
|
CollectionService:AddTag(instance, tagName)
|
|
1270
|
-
ChangeHistoryService:SetWaypoint(`Add tag {tagName} to {instance.Name}`)
|
|
1271
2327
|
return {
|
|
1272
2328
|
success = true,
|
|
1273
2329
|
instancePath = instancePath,
|
|
@@ -1277,8 +2333,10 @@ local function addTag(requestData)
|
|
|
1277
2333
|
}
|
|
1278
2334
|
end)
|
|
1279
2335
|
if success then
|
|
2336
|
+
finishRecording(recordingId, true)
|
|
1280
2337
|
return result
|
|
1281
2338
|
end
|
|
2339
|
+
finishRecording(recordingId, false)
|
|
1282
2340
|
return {
|
|
1283
2341
|
error = `Failed to add tag: {result}`,
|
|
1284
2342
|
}
|
|
@@ -1297,10 +2355,10 @@ local function removeTag(requestData)
|
|
|
1297
2355
|
error = `Instance not found: {instancePath}`,
|
|
1298
2356
|
}
|
|
1299
2357
|
end
|
|
2358
|
+
local recordingId = beginRecording(`Remove tag {tagName} from {instance.Name}`)
|
|
1300
2359
|
local success, result = pcall(function()
|
|
1301
2360
|
local hadTag = CollectionService:HasTag(instance, tagName)
|
|
1302
2361
|
CollectionService:RemoveTag(instance, tagName)
|
|
1303
|
-
ChangeHistoryService:SetWaypoint(`Remove tag {tagName} from {instance.Name}`)
|
|
1304
2362
|
return {
|
|
1305
2363
|
success = true,
|
|
1306
2364
|
instancePath = instancePath,
|
|
@@ -1310,8 +2368,10 @@ local function removeTag(requestData)
|
|
|
1310
2368
|
}
|
|
1311
2369
|
end)
|
|
1312
2370
|
if success then
|
|
2371
|
+
finishRecording(recordingId, true)
|
|
1313
2372
|
return result
|
|
1314
2373
|
end
|
|
2374
|
+
finishRecording(recordingId, false)
|
|
1315
2375
|
return {
|
|
1316
2376
|
error = `Failed to remove tag: {result}`,
|
|
1317
2377
|
}
|
|
@@ -1442,6 +2502,36 @@ local function executeLuau(requestData)
|
|
|
1442
2502
|
}
|
|
1443
2503
|
end
|
|
1444
2504
|
end
|
|
2505
|
+
local function undo(_requestData)
|
|
2506
|
+
local success, result = pcall(function()
|
|
2507
|
+
ChangeHistoryService:Undo()
|
|
2508
|
+
return {
|
|
2509
|
+
success = true,
|
|
2510
|
+
message = "Undo executed successfully",
|
|
2511
|
+
}
|
|
2512
|
+
end)
|
|
2513
|
+
if success then
|
|
2514
|
+
return result
|
|
2515
|
+
end
|
|
2516
|
+
return {
|
|
2517
|
+
error = `Failed to undo: {result}`,
|
|
2518
|
+
}
|
|
2519
|
+
end
|
|
2520
|
+
local function redo(_requestData)
|
|
2521
|
+
local success, result = pcall(function()
|
|
2522
|
+
ChangeHistoryService:Redo()
|
|
2523
|
+
return {
|
|
2524
|
+
success = true,
|
|
2525
|
+
message = "Redo executed successfully",
|
|
2526
|
+
}
|
|
2527
|
+
end)
|
|
2528
|
+
if success then
|
|
2529
|
+
return result
|
|
2530
|
+
end
|
|
2531
|
+
return {
|
|
2532
|
+
error = `Failed to redo: {result}`,
|
|
2533
|
+
}
|
|
2534
|
+
end
|
|
1445
2535
|
return {
|
|
1446
2536
|
getAttribute = getAttribute,
|
|
1447
2537
|
setAttribute = setAttribute,
|
|
@@ -1453,21 +2543,26 @@ return {
|
|
|
1453
2543
|
getTagged = getTagged,
|
|
1454
2544
|
getSelection = getSelection,
|
|
1455
2545
|
executeLuau = executeLuau,
|
|
2546
|
+
undo = undo,
|
|
2547
|
+
redo = redo,
|
|
1456
2548
|
}
|
|
1457
2549
|
]]></string>
|
|
1458
2550
|
</Properties>
|
|
1459
2551
|
</Item>
|
|
1460
|
-
<Item class="ModuleScript" referent="
|
|
2552
|
+
<Item class="ModuleScript" referent="9">
|
|
1461
2553
|
<Properties>
|
|
1462
2554
|
<string name="Name">PropertyHandlers</string>
|
|
1463
2555
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
1464
2556
|
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
1465
2557
|
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
1466
|
-
local
|
|
2558
|
+
local Recording = TS.import(script, script.Parent.Parent, "Recording")
|
|
1467
2559
|
local _binding = Utils
|
|
1468
2560
|
local getInstanceByPath = _binding.getInstanceByPath
|
|
1469
2561
|
local convertPropertyValue = _binding.convertPropertyValue
|
|
1470
2562
|
local evaluateFormula = _binding.evaluateFormula
|
|
2563
|
+
local _binding_1 = Recording
|
|
2564
|
+
local beginRecording = _binding_1.beginRecording
|
|
2565
|
+
local finishRecording = _binding_1.finishRecording
|
|
1471
2566
|
local function setProperty(requestData)
|
|
1472
2567
|
local instancePath = requestData.instancePath
|
|
1473
2568
|
local propertyName = requestData.propertyName
|
|
@@ -1483,6 +2578,7 @@ local function setProperty(requestData)
|
|
|
1483
2578
|
error = `Instance not found: {instancePath}`,
|
|
1484
2579
|
}
|
|
1485
2580
|
end
|
|
2581
|
+
local recordingId = beginRecording(`Set {propertyName} property`)
|
|
1486
2582
|
local inst = instance
|
|
1487
2583
|
local success, result = pcall(function()
|
|
1488
2584
|
if propertyName == "Parent" or propertyName == "PrimaryPart" then
|
|
@@ -1508,10 +2604,10 @@ local function setProperty(requestData)
|
|
|
1508
2604
|
inst[propertyName] = propertyValue
|
|
1509
2605
|
end
|
|
1510
2606
|
end
|
|
1511
|
-
ChangeHistoryService:SetWaypoint(`Set {propertyName} property`)
|
|
1512
2607
|
return true
|
|
1513
2608
|
end)
|
|
1514
2609
|
if success then
|
|
2610
|
+
finishRecording(recordingId, true)
|
|
1515
2611
|
return {
|
|
1516
2612
|
success = true,
|
|
1517
2613
|
instancePath = instancePath,
|
|
@@ -1520,6 +2616,7 @@ local function setProperty(requestData)
|
|
|
1520
2616
|
message = "Property set successfully",
|
|
1521
2617
|
}
|
|
1522
2618
|
else
|
|
2619
|
+
finishRecording(recordingId, false)
|
|
1523
2620
|
return {
|
|
1524
2621
|
error = `Failed to set property: {result}`,
|
|
1525
2622
|
instancePath = instancePath,
|
|
@@ -1539,6 +2636,7 @@ local function massSetProperty(requestData)
|
|
|
1539
2636
|
local results = {}
|
|
1540
2637
|
local successCount = 0
|
|
1541
2638
|
local failureCount = 0
|
|
2639
|
+
local recordingId = beginRecording(`Mass set {propertyName} property`)
|
|
1542
2640
|
for _, path in paths do
|
|
1543
2641
|
local instance = getInstanceByPath(path)
|
|
1544
2642
|
if instance then
|
|
@@ -1573,9 +2671,7 @@ local function massSetProperty(requestData)
|
|
|
1573
2671
|
table.insert(results, _arg0)
|
|
1574
2672
|
end
|
|
1575
2673
|
end
|
|
1576
|
-
|
|
1577
|
-
ChangeHistoryService:SetWaypoint(`Mass set {propertyName} property`)
|
|
1578
|
-
end
|
|
2674
|
+
finishRecording(recordingId, successCount > 0)
|
|
1579
2675
|
return {
|
|
1580
2676
|
results = results,
|
|
1581
2677
|
summary = {
|
|
@@ -1643,6 +2739,7 @@ local function setCalculatedProperty(requestData)
|
|
|
1643
2739
|
local results = {}
|
|
1644
2740
|
local successCount = 0
|
|
1645
2741
|
local failureCount = 0
|
|
2742
|
+
local recordingId = beginRecording(`Set calculated {propertyName} property`)
|
|
1646
2743
|
for i = 0, #paths - 1 do
|
|
1647
2744
|
local path = paths[i + 1]
|
|
1648
2745
|
local instance = getInstanceByPath(path)
|
|
@@ -1695,9 +2792,7 @@ local function setCalculatedProperty(requestData)
|
|
|
1695
2792
|
table.insert(results, _arg0)
|
|
1696
2793
|
end
|
|
1697
2794
|
end
|
|
1698
|
-
|
|
1699
|
-
ChangeHistoryService:SetWaypoint(`Set calculated {propertyName} property`)
|
|
1700
|
-
end
|
|
2795
|
+
finishRecording(recordingId, successCount > 0)
|
|
1701
2796
|
return {
|
|
1702
2797
|
results = results,
|
|
1703
2798
|
summary = {
|
|
@@ -1722,6 +2817,7 @@ local function setRelativeProperty(requestData)
|
|
|
1722
2817
|
local results = {}
|
|
1723
2818
|
local successCount = 0
|
|
1724
2819
|
local failureCount = 0
|
|
2820
|
+
local recordingId = beginRecording(`Set relative {propertyName} property`)
|
|
1725
2821
|
local function applyOp(current, op, val)
|
|
1726
2822
|
if op == "add" then
|
|
1727
2823
|
return current + val
|
|
@@ -1834,9 +2930,7 @@ local function setRelativeProperty(requestData)
|
|
|
1834
2930
|
table.insert(results, _arg0)
|
|
1835
2931
|
end
|
|
1836
2932
|
end
|
|
1837
|
-
|
|
1838
|
-
ChangeHistoryService:SetWaypoint(`Set relative {propertyName} property`)
|
|
1839
|
-
end
|
|
2933
|
+
finishRecording(recordingId, successCount > 0)
|
|
1840
2934
|
return {
|
|
1841
2935
|
results = results,
|
|
1842
2936
|
summary = {
|
|
@@ -1858,7 +2952,7 @@ return {
|
|
|
1858
2952
|
]]></string>
|
|
1859
2953
|
</Properties>
|
|
1860
2954
|
</Item>
|
|
1861
|
-
<Item class="ModuleScript" referent="
|
|
2955
|
+
<Item class="ModuleScript" referent="10">
|
|
1862
2956
|
<Properties>
|
|
1863
2957
|
<string name="Name">QueryHandlers</string>
|
|
1864
2958
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -1897,6 +2991,9 @@ local function getFileTree(requestData)
|
|
|
1897
2991
|
if instance:IsA("LuaSourceContainer") then
|
|
1898
2992
|
node.hasSource = true
|
|
1899
2993
|
node.scriptType = instance.ClassName
|
|
2994
|
+
if instance:IsA("BaseScript") then
|
|
2995
|
+
node.enabled = instance.Enabled
|
|
2996
|
+
end
|
|
1900
2997
|
end
|
|
1901
2998
|
for _, child in instance:GetChildren() do
|
|
1902
2999
|
local _children = node.children
|
|
@@ -1939,13 +3036,16 @@ local function searchFiles(requestData)
|
|
|
1939
3036
|
match = (string.find(_exp, _arg0)) ~= nil
|
|
1940
3037
|
end
|
|
1941
3038
|
if match then
|
|
1942
|
-
local
|
|
3039
|
+
local entry = {
|
|
1943
3040
|
name = instance.Name,
|
|
1944
3041
|
className = instance.ClassName,
|
|
1945
3042
|
path = getInstancePath(instance),
|
|
1946
3043
|
hasSource = instance:IsA("LuaSourceContainer"),
|
|
1947
3044
|
}
|
|
1948
|
-
|
|
3045
|
+
if instance:IsA("BaseScript") then
|
|
3046
|
+
entry.enabled = instance.Enabled
|
|
3047
|
+
end
|
|
3048
|
+
table.insert(results, entry)
|
|
1949
3049
|
end
|
|
1950
3050
|
for _, child in instance:GetChildren() do
|
|
1951
3051
|
searchRecursive(child)
|
|
@@ -2072,6 +3172,11 @@ local function searchObjects(requestData)
|
|
|
2072
3172
|
end
|
|
2073
3173
|
local function getInstanceProperties(requestData)
|
|
2074
3174
|
local instancePath = requestData.instancePath
|
|
3175
|
+
local _condition = (requestData.excludeSource)
|
|
3176
|
+
if _condition == nil then
|
|
3177
|
+
_condition = false
|
|
3178
|
+
end
|
|
3179
|
+
local excludeSource = _condition
|
|
2075
3180
|
if not (instancePath ~= "" and instancePath) then
|
|
2076
3181
|
return {
|
|
2077
3182
|
error = "Instance path is required",
|
|
@@ -2127,7 +3232,13 @@ local function getInstanceProperties(requestData)
|
|
|
2127
3232
|
end
|
|
2128
3233
|
end
|
|
2129
3234
|
if instance:IsA("LuaSourceContainer") then
|
|
2130
|
-
|
|
3235
|
+
if not excludeSource then
|
|
3236
|
+
properties.Source = readScriptSource(instance)
|
|
3237
|
+
else
|
|
3238
|
+
local src = readScriptSource(instance)
|
|
3239
|
+
properties.SourceLength = #src
|
|
3240
|
+
properties.LineCount = #(Utils.splitLines(src))
|
|
3241
|
+
end
|
|
2131
3242
|
if instance:IsA("BaseScript") then
|
|
2132
3243
|
properties.Enabled = tostring(instance.Enabled)
|
|
2133
3244
|
end
|
|
@@ -2195,14 +3306,17 @@ local function getInstanceChildren(requestData)
|
|
|
2195
3306
|
end
|
|
2196
3307
|
local children = {}
|
|
2197
3308
|
for _, child in instance:GetChildren() do
|
|
2198
|
-
local
|
|
3309
|
+
local entry = {
|
|
2199
3310
|
name = child.Name,
|
|
2200
3311
|
className = child.ClassName,
|
|
2201
3312
|
path = getInstancePath(child),
|
|
2202
3313
|
hasChildren = #child:GetChildren() > 0,
|
|
2203
3314
|
hasSource = child:IsA("LuaSourceContainer"),
|
|
2204
3315
|
}
|
|
2205
|
-
|
|
3316
|
+
if child:IsA("BaseScript") then
|
|
3317
|
+
entry.enabled = child.Enabled
|
|
3318
|
+
end
|
|
3319
|
+
table.insert(children, entry)
|
|
2206
3320
|
end
|
|
2207
3321
|
return {
|
|
2208
3322
|
instancePath = instancePath,
|
|
@@ -2494,6 +3608,192 @@ local function getProjectStructure(requestData)
|
|
|
2494
3608
|
result.timestamp = tick()
|
|
2495
3609
|
return result
|
|
2496
3610
|
end
|
|
3611
|
+
local function grepScripts(requestData)
|
|
3612
|
+
local pattern = requestData.pattern
|
|
3613
|
+
if not (pattern ~= "" and pattern) then
|
|
3614
|
+
return {
|
|
3615
|
+
error = "pattern is required",
|
|
3616
|
+
}
|
|
3617
|
+
end
|
|
3618
|
+
local _condition = (requestData.caseSensitive)
|
|
3619
|
+
if _condition == nil then
|
|
3620
|
+
_condition = false
|
|
3621
|
+
end
|
|
3622
|
+
local caseSensitive = _condition
|
|
3623
|
+
local _condition_1 = (requestData.contextLines)
|
|
3624
|
+
if _condition_1 == nil then
|
|
3625
|
+
_condition_1 = 0
|
|
3626
|
+
end
|
|
3627
|
+
local contextLines = _condition_1
|
|
3628
|
+
local _condition_2 = (requestData.maxResults)
|
|
3629
|
+
if _condition_2 == nil then
|
|
3630
|
+
_condition_2 = 100
|
|
3631
|
+
end
|
|
3632
|
+
local maxResults = _condition_2
|
|
3633
|
+
local _condition_3 = (requestData.maxResultsPerScript)
|
|
3634
|
+
if _condition_3 == nil then
|
|
3635
|
+
_condition_3 = 0
|
|
3636
|
+
end
|
|
3637
|
+
local maxResultsPerScript = _condition_3
|
|
3638
|
+
local _condition_4 = (requestData.usePattern)
|
|
3639
|
+
if _condition_4 == nil then
|
|
3640
|
+
_condition_4 = false
|
|
3641
|
+
end
|
|
3642
|
+
local usePattern = _condition_4
|
|
3643
|
+
local _condition_5 = (requestData.filesOnly)
|
|
3644
|
+
if _condition_5 == nil then
|
|
3645
|
+
_condition_5 = false
|
|
3646
|
+
end
|
|
3647
|
+
local filesOnly = _condition_5
|
|
3648
|
+
local _condition_6 = (requestData.path)
|
|
3649
|
+
if _condition_6 == nil then
|
|
3650
|
+
_condition_6 = ""
|
|
3651
|
+
end
|
|
3652
|
+
local searchPath = _condition_6
|
|
3653
|
+
local classFilter = requestData.classFilter
|
|
3654
|
+
local startInstance = if searchPath ~= "" then getInstanceByPath(searchPath) else game
|
|
3655
|
+
if not startInstance then
|
|
3656
|
+
return {
|
|
3657
|
+
error = `Path not found: {searchPath}`,
|
|
3658
|
+
}
|
|
3659
|
+
end
|
|
3660
|
+
-- Prepare pattern for matching
|
|
3661
|
+
local searchPattern = if caseSensitive then pattern else string.lower(pattern)
|
|
3662
|
+
local results = {}
|
|
3663
|
+
local totalMatches = 0
|
|
3664
|
+
local scriptsSearched = 0
|
|
3665
|
+
local hitLimit = false
|
|
3666
|
+
local function searchInstance(instance)
|
|
3667
|
+
if hitLimit then
|
|
3668
|
+
return nil
|
|
3669
|
+
end
|
|
3670
|
+
if instance:IsA("LuaSourceContainer") then
|
|
3671
|
+
-- Apply class filter
|
|
3672
|
+
if classFilter ~= "" and classFilter then
|
|
3673
|
+
local _exp = string.lower(instance.ClassName)
|
|
3674
|
+
local _arg0 = string.lower(classFilter)
|
|
3675
|
+
local _value = (string.find(_exp, _arg0))
|
|
3676
|
+
if not (_value ~= 0 and _value == _value and _value) then
|
|
3677
|
+
return nil
|
|
3678
|
+
end
|
|
3679
|
+
end
|
|
3680
|
+
scriptsSearched += 1
|
|
3681
|
+
local source = readScriptSource(instance)
|
|
3682
|
+
local lines = Utils.splitLines(source)
|
|
3683
|
+
local scriptMatches = {}
|
|
3684
|
+
local scriptMatchCount = 0
|
|
3685
|
+
for i = 0, #lines - 1 do
|
|
3686
|
+
if hitLimit then
|
|
3687
|
+
break
|
|
3688
|
+
end
|
|
3689
|
+
if maxResultsPerScript > 0 and scriptMatchCount >= maxResultsPerScript then
|
|
3690
|
+
break
|
|
3691
|
+
end
|
|
3692
|
+
local line = lines[i + 1]
|
|
3693
|
+
local searchLine = if caseSensitive then line else string.lower(line)
|
|
3694
|
+
local matchStart
|
|
3695
|
+
local matchEnd
|
|
3696
|
+
if usePattern then
|
|
3697
|
+
matchStart, matchEnd = string.find(searchLine, searchPattern)
|
|
3698
|
+
else
|
|
3699
|
+
matchStart, matchEnd = string.find(searchLine, searchPattern, 1, true)
|
|
3700
|
+
end
|
|
3701
|
+
if matchStart ~= nil then
|
|
3702
|
+
scriptMatchCount += 1
|
|
3703
|
+
totalMatches += 1
|
|
3704
|
+
if totalMatches > maxResults then
|
|
3705
|
+
hitLimit = true
|
|
3706
|
+
break
|
|
3707
|
+
end
|
|
3708
|
+
if not filesOnly then
|
|
3709
|
+
-- Gather context lines
|
|
3710
|
+
local before = {}
|
|
3711
|
+
local after = {}
|
|
3712
|
+
if contextLines > 0 then
|
|
3713
|
+
local beforeStart = math.max(0, i - contextLines)
|
|
3714
|
+
do
|
|
3715
|
+
local j = beforeStart
|
|
3716
|
+
local _shouldIncrement = false
|
|
3717
|
+
while true do
|
|
3718
|
+
if _shouldIncrement then
|
|
3719
|
+
j += 1
|
|
3720
|
+
else
|
|
3721
|
+
_shouldIncrement = true
|
|
3722
|
+
end
|
|
3723
|
+
if not (j < i) then
|
|
3724
|
+
break
|
|
3725
|
+
end
|
|
3726
|
+
local _arg0 = lines[j + 1]
|
|
3727
|
+
table.insert(before, _arg0)
|
|
3728
|
+
end
|
|
3729
|
+
end
|
|
3730
|
+
local afterEnd = math.min(#lines - 1, i + contextLines)
|
|
3731
|
+
do
|
|
3732
|
+
local j = i + 1
|
|
3733
|
+
local _shouldIncrement = false
|
|
3734
|
+
while true do
|
|
3735
|
+
if _shouldIncrement then
|
|
3736
|
+
j += 1
|
|
3737
|
+
else
|
|
3738
|
+
_shouldIncrement = true
|
|
3739
|
+
end
|
|
3740
|
+
if not (j <= afterEnd) then
|
|
3741
|
+
break
|
|
3742
|
+
end
|
|
3743
|
+
local _arg0 = lines[j + 1]
|
|
3744
|
+
table.insert(after, _arg0)
|
|
3745
|
+
end
|
|
3746
|
+
end
|
|
3747
|
+
end
|
|
3748
|
+
local _arg0 = {
|
|
3749
|
+
line = i + 1,
|
|
3750
|
+
column = matchStart,
|
|
3751
|
+
text = line,
|
|
3752
|
+
before = before,
|
|
3753
|
+
after = after,
|
|
3754
|
+
}
|
|
3755
|
+
table.insert(scriptMatches, _arg0)
|
|
3756
|
+
end
|
|
3757
|
+
end
|
|
3758
|
+
end
|
|
3759
|
+
if scriptMatchCount > 0 then
|
|
3760
|
+
local scriptResult = {
|
|
3761
|
+
instancePath = getInstancePath(instance),
|
|
3762
|
+
name = instance.Name,
|
|
3763
|
+
className = instance.ClassName,
|
|
3764
|
+
matches = scriptMatches,
|
|
3765
|
+
}
|
|
3766
|
+
if instance:IsA("BaseScript") then
|
|
3767
|
+
scriptResult.enabled = instance.Enabled
|
|
3768
|
+
end
|
|
3769
|
+
table.insert(results, scriptResult)
|
|
3770
|
+
end
|
|
3771
|
+
end
|
|
3772
|
+
for _, child in instance:GetChildren() do
|
|
3773
|
+
if hitLimit then
|
|
3774
|
+
return nil
|
|
3775
|
+
end
|
|
3776
|
+
searchInstance(child)
|
|
3777
|
+
end
|
|
3778
|
+
end
|
|
3779
|
+
searchInstance(startInstance)
|
|
3780
|
+
return {
|
|
3781
|
+
results = results,
|
|
3782
|
+
pattern = pattern,
|
|
3783
|
+
totalMatches = if hitLimit then `>{maxResults}` else totalMatches,
|
|
3784
|
+
scriptsSearched = scriptsSearched,
|
|
3785
|
+
scriptsMatched = #results,
|
|
3786
|
+
truncated = hitLimit,
|
|
3787
|
+
options = {
|
|
3788
|
+
caseSensitive = caseSensitive,
|
|
3789
|
+
contextLines = contextLines,
|
|
3790
|
+
usePattern = usePattern,
|
|
3791
|
+
filesOnly = filesOnly,
|
|
3792
|
+
maxResults = maxResults,
|
|
3793
|
+
maxResultsPerScript = maxResultsPerScript,
|
|
3794
|
+
},
|
|
3795
|
+
}
|
|
3796
|
+
end
|
|
2497
3797
|
return {
|
|
2498
3798
|
getFileTree = getFileTree,
|
|
2499
3799
|
searchFiles = searchFiles,
|
|
@@ -2505,17 +3805,18 @@ return {
|
|
|
2505
3805
|
searchByProperty = searchByProperty,
|
|
2506
3806
|
getClassInfo = getClassInfo,
|
|
2507
3807
|
getProjectStructure = getProjectStructure,
|
|
3808
|
+
grepScripts = grepScripts,
|
|
2508
3809
|
}
|
|
2509
3810
|
]]></string>
|
|
2510
3811
|
</Properties>
|
|
2511
3812
|
</Item>
|
|
2512
|
-
<Item class="ModuleScript" referent="
|
|
3813
|
+
<Item class="ModuleScript" referent="11">
|
|
2513
3814
|
<Properties>
|
|
2514
3815
|
<string name="Name">ScriptHandlers</string>
|
|
2515
3816
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
2516
3817
|
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
2517
3818
|
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
2518
|
-
local
|
|
3819
|
+
local Recording = TS.import(script, script.Parent.Parent, "Recording")
|
|
2519
3820
|
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
2520
3821
|
local _binding = Utils
|
|
2521
3822
|
local getInstancePath = _binding.getInstancePath
|
|
@@ -2523,11 +3824,15 @@ local getInstanceByPath = _binding.getInstanceByPath
|
|
|
2523
3824
|
local readScriptSource = _binding.readScriptSource
|
|
2524
3825
|
local splitLines = _binding.splitLines
|
|
2525
3826
|
local joinLines = _binding.joinLines
|
|
3827
|
+
local _binding_1 = Recording
|
|
3828
|
+
local beginRecording = _binding_1.beginRecording
|
|
3829
|
+
local finishRecording = _binding_1.finishRecording
|
|
2526
3830
|
local function normalizeEscapes(s)
|
|
2527
3831
|
local result = s
|
|
2528
3832
|
result = (string.gsub(result, "\\n", "\n"))
|
|
2529
3833
|
result = (string.gsub(result, "\\t", "\t"))
|
|
2530
3834
|
result = (string.gsub(result, "\\r", "\r"))
|
|
3835
|
+
result = (string.gsub(result, '\\"', '"'))
|
|
2531
3836
|
result = (string.gsub(result, "\\\\", "\\"))
|
|
2532
3837
|
return result
|
|
2533
3838
|
end
|
|
@@ -2679,12 +3984,12 @@ local function setScriptSource(requestData)
|
|
|
2679
3984
|
}
|
|
2680
3985
|
end
|
|
2681
3986
|
local sourceToSet = normalizeEscapes(newSource)
|
|
3987
|
+
local recordingId = beginRecording(`Set script source: {instance.Name}`)
|
|
2682
3988
|
local updateSuccess, updateResult = pcall(function()
|
|
2683
3989
|
local oldSourceLength = #readScriptSource(instance)
|
|
2684
3990
|
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
2685
3991
|
return sourceToSet
|
|
2686
3992
|
end)
|
|
2687
|
-
ChangeHistoryService:SetWaypoint(`Set script source: {instance.Name}`)
|
|
2688
3993
|
return {
|
|
2689
3994
|
success = true,
|
|
2690
3995
|
instancePath = instancePath,
|
|
@@ -2695,12 +4000,12 @@ local function setScriptSource(requestData)
|
|
|
2695
4000
|
}
|
|
2696
4001
|
end)
|
|
2697
4002
|
if updateSuccess then
|
|
4003
|
+
finishRecording(recordingId, true)
|
|
2698
4004
|
return updateResult
|
|
2699
4005
|
end
|
|
2700
4006
|
local directSuccess, directResult = pcall(function()
|
|
2701
4007
|
local oldSource = instance.Source
|
|
2702
4008
|
instance.Source = sourceToSet
|
|
2703
|
-
ChangeHistoryService:SetWaypoint(`Set script source: {instance.Name}`)
|
|
2704
4009
|
return {
|
|
2705
4010
|
success = true,
|
|
2706
4011
|
instancePath = instancePath,
|
|
@@ -2711,6 +4016,7 @@ local function setScriptSource(requestData)
|
|
|
2711
4016
|
}
|
|
2712
4017
|
end)
|
|
2713
4018
|
if directSuccess then
|
|
4019
|
+
finishRecording(recordingId, true)
|
|
2714
4020
|
return directResult
|
|
2715
4021
|
end
|
|
2716
4022
|
local replaceSuccess, replaceResult = pcall(function()
|
|
@@ -2727,7 +4033,6 @@ local function setScriptSource(requestData)
|
|
|
2727
4033
|
end
|
|
2728
4034
|
newScript.Parent = parent
|
|
2729
4035
|
instance:Destroy()
|
|
2730
|
-
ChangeHistoryService:SetWaypoint(`Replace script: {name}`)
|
|
2731
4036
|
return {
|
|
2732
4037
|
success = true,
|
|
2733
4038
|
instancePath = getInstancePath(newScript),
|
|
@@ -2736,8 +4041,10 @@ local function setScriptSource(requestData)
|
|
|
2736
4041
|
}
|
|
2737
4042
|
end)
|
|
2738
4043
|
if replaceSuccess then
|
|
4044
|
+
finishRecording(recordingId, true)
|
|
2739
4045
|
return replaceResult
|
|
2740
4046
|
end
|
|
4047
|
+
finishRecording(recordingId, false)
|
|
2741
4048
|
return {
|
|
2742
4049
|
error = `Failed to set script source. UpdateSourceAsync failed: {updateResult}. Direct assignment failed: {directResult}. Replace method failed: {replaceResult}`,
|
|
2743
4050
|
}
|
|
@@ -2764,6 +4071,7 @@ local function editScriptLines(requestData)
|
|
|
2764
4071
|
error = `Instance is not a script-like object: {instance.ClassName}`,
|
|
2765
4072
|
}
|
|
2766
4073
|
end
|
|
4074
|
+
local recordingId = beginRecording(`Edit script lines {startLine}-{endLine}: {instance.Name}`)
|
|
2767
4075
|
local success, result = pcall(function()
|
|
2768
4076
|
local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
|
|
2769
4077
|
local totalLines = #lines
|
|
@@ -2814,7 +4122,6 @@ local function editScriptLines(requestData)
|
|
|
2814
4122
|
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
2815
4123
|
return newSource
|
|
2816
4124
|
end)
|
|
2817
|
-
ChangeHistoryService:SetWaypoint(`Edit script lines {startLine}-{endLine}: {instance.Name}`)
|
|
2818
4125
|
return {
|
|
2819
4126
|
success = true,
|
|
2820
4127
|
instancePath = instancePath,
|
|
@@ -2829,8 +4136,10 @@ local function editScriptLines(requestData)
|
|
|
2829
4136
|
}
|
|
2830
4137
|
end)
|
|
2831
4138
|
if success then
|
|
4139
|
+
finishRecording(recordingId, true)
|
|
2832
4140
|
return result
|
|
2833
4141
|
end
|
|
4142
|
+
finishRecording(recordingId, false)
|
|
2834
4143
|
return {
|
|
2835
4144
|
error = `Failed to edit script lines: {result}`,
|
|
2836
4145
|
}
|
|
@@ -2860,6 +4169,7 @@ local function insertScriptLines(requestData)
|
|
|
2860
4169
|
error = `Instance is not a script-like object: {instance.ClassName}`,
|
|
2861
4170
|
}
|
|
2862
4171
|
end
|
|
4172
|
+
local recordingId = beginRecording(`Insert script lines after line {afterLine}: {instance.Name}`)
|
|
2863
4173
|
local success, result = pcall(function()
|
|
2864
4174
|
local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
|
|
2865
4175
|
local totalLines = #lines
|
|
@@ -2907,7 +4217,6 @@ local function insertScriptLines(requestData)
|
|
|
2907
4217
|
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
2908
4218
|
return newSource
|
|
2909
4219
|
end)
|
|
2910
|
-
ChangeHistoryService:SetWaypoint(`Insert script lines after line {afterLine}: {instance.Name}`)
|
|
2911
4220
|
return {
|
|
2912
4221
|
success = true,
|
|
2913
4222
|
instancePath = instancePath,
|
|
@@ -2918,8 +4227,10 @@ local function insertScriptLines(requestData)
|
|
|
2918
4227
|
}
|
|
2919
4228
|
end)
|
|
2920
4229
|
if success then
|
|
4230
|
+
finishRecording(recordingId, true)
|
|
2921
4231
|
return result
|
|
2922
4232
|
end
|
|
4233
|
+
finishRecording(recordingId, false)
|
|
2923
4234
|
return {
|
|
2924
4235
|
error = `Failed to insert script lines: {result}`,
|
|
2925
4236
|
}
|
|
@@ -2944,6 +4255,7 @@ local function deleteScriptLines(requestData)
|
|
|
2944
4255
|
error = `Instance is not a script-like object: {instance.ClassName}`,
|
|
2945
4256
|
}
|
|
2946
4257
|
end
|
|
4258
|
+
local recordingId = beginRecording(`Delete script lines {startLine}-{endLine}: {instance.Name}`)
|
|
2947
4259
|
local success, result = pcall(function()
|
|
2948
4260
|
local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
|
|
2949
4261
|
local totalLines = #lines
|
|
@@ -2990,7 +4302,6 @@ local function deleteScriptLines(requestData)
|
|
|
2990
4302
|
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
2991
4303
|
return newSource
|
|
2992
4304
|
end)
|
|
2993
|
-
ChangeHistoryService:SetWaypoint(`Delete script lines {startLine}-{endLine}: {instance.Name}`)
|
|
2994
4305
|
return {
|
|
2995
4306
|
success = true,
|
|
2996
4307
|
instancePath = instancePath,
|
|
@@ -3004,8 +4315,10 @@ local function deleteScriptLines(requestData)
|
|
|
3004
4315
|
}
|
|
3005
4316
|
end)
|
|
3006
4317
|
if success then
|
|
4318
|
+
finishRecording(recordingId, true)
|
|
3007
4319
|
return result
|
|
3008
4320
|
end
|
|
4321
|
+
finishRecording(recordingId, false)
|
|
3009
4322
|
return {
|
|
3010
4323
|
error = `Failed to delete script lines: {result}`,
|
|
3011
4324
|
}
|
|
@@ -3020,7 +4333,7 @@ return {
|
|
|
3020
4333
|
]]></string>
|
|
3021
4334
|
</Properties>
|
|
3022
4335
|
</Item>
|
|
3023
|
-
<Item class="ModuleScript" referent="
|
|
4336
|
+
<Item class="ModuleScript" referent="12">
|
|
3024
4337
|
<Properties>
|
|
3025
4338
|
<string name="Name">TestHandlers</string>
|
|
3026
4339
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3169,11 +4482,41 @@ return {
|
|
|
3169
4482
|
</Properties>
|
|
3170
4483
|
</Item>
|
|
3171
4484
|
</Item>
|
|
3172
|
-
<Item class="ModuleScript" referent="
|
|
4485
|
+
<Item class="ModuleScript" referent="13">
|
|
4486
|
+
<Properties>
|
|
4487
|
+
<string name="Name">Recording</string>
|
|
4488
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
4489
|
+
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
4490
|
+
local function beginRecording(actionName)
|
|
4491
|
+
local success, result = pcall(function()
|
|
4492
|
+
return ChangeHistoryService:TryBeginRecording(`MCP: {actionName}`)
|
|
4493
|
+
end)
|
|
4494
|
+
if success then
|
|
4495
|
+
return result
|
|
4496
|
+
end
|
|
4497
|
+
return nil
|
|
4498
|
+
end
|
|
4499
|
+
local function finishRecording(recordingId, shouldCommit)
|
|
4500
|
+
if recordingId == nil then
|
|
4501
|
+
return nil
|
|
4502
|
+
end
|
|
4503
|
+
local operation = if shouldCommit then Enum.FinishRecordingOperation.Commit else Enum.FinishRecordingOperation.Cancel
|
|
4504
|
+
pcall(function()
|
|
4505
|
+
ChangeHistoryService:FinishRecording(recordingId, operation)
|
|
4506
|
+
end)
|
|
4507
|
+
end
|
|
4508
|
+
return {
|
|
4509
|
+
beginRecording = beginRecording,
|
|
4510
|
+
finishRecording = finishRecording,
|
|
4511
|
+
}
|
|
4512
|
+
]]></string>
|
|
4513
|
+
</Properties>
|
|
4514
|
+
</Item>
|
|
4515
|
+
<Item class="ModuleScript" referent="14">
|
|
3173
4516
|
<Properties>
|
|
3174
4517
|
<string name="Name">State</string>
|
|
3175
4518
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
3176
|
-
local CURRENT_VERSION = "2.
|
|
4519
|
+
local CURRENT_VERSION = "2.5.0"
|
|
3177
4520
|
local MAX_CONNECTIONS = 5
|
|
3178
4521
|
local BASE_PORT = 58741
|
|
3179
4522
|
local activeTabIndex = 0
|
|
@@ -3260,7 +4603,7 @@ return {
|
|
|
3260
4603
|
]]></string>
|
|
3261
4604
|
</Properties>
|
|
3262
4605
|
</Item>
|
|
3263
|
-
<Item class="ModuleScript" referent="
|
|
4606
|
+
<Item class="ModuleScript" referent="15">
|
|
3264
4607
|
<Properties>
|
|
3265
4608
|
<string name="Name">UI</string>
|
|
3266
4609
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3888,7 +5231,7 @@ return {
|
|
|
3888
5231
|
]]></string>
|
|
3889
5232
|
</Properties>
|
|
3890
5233
|
</Item>
|
|
3891
|
-
<Item class="ModuleScript" referent="
|
|
5234
|
+
<Item class="ModuleScript" referent="16">
|
|
3892
5235
|
<Properties>
|
|
3893
5236
|
<string name="Name">Utils</string>
|
|
3894
5237
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3927,14 +5270,10 @@ local function getInstanceByPath(path)
|
|
|
3927
5270
|
end
|
|
3928
5271
|
local current = game
|
|
3929
5272
|
for _, part in parts do
|
|
3930
|
-
local _result = current
|
|
3931
|
-
if _result ~= nil then
|
|
3932
|
-
_result = _result:FindFirstChild(part)
|
|
3933
|
-
end
|
|
3934
|
-
current = _result
|
|
3935
5273
|
if not current then
|
|
3936
5274
|
return nil
|
|
3937
5275
|
end
|
|
5276
|
+
current = current:FindFirstChild(part)
|
|
3938
5277
|
end
|
|
3939
5278
|
return current
|
|
3940
5279
|
end
|
|
@@ -4422,11 +5761,11 @@ return {
|
|
|
4422
5761
|
</Properties>
|
|
4423
5762
|
</Item>
|
|
4424
5763
|
</Item>
|
|
4425
|
-
<Item class="Folder" referent="
|
|
5764
|
+
<Item class="Folder" referent="20">
|
|
4426
5765
|
<Properties>
|
|
4427
5766
|
<string name="Name">include</string>
|
|
4428
5767
|
</Properties>
|
|
4429
|
-
<Item class="ModuleScript" referent="
|
|
5768
|
+
<Item class="ModuleScript" referent="17">
|
|
4430
5769
|
<Properties>
|
|
4431
5770
|
<string name="Name">Promise</string>
|
|
4432
5771
|
<string name="Source"><![CDATA[--[[
|
|
@@ -6500,7 +7839,7 @@ return Promise
|
|
|
6500
7839
|
]]></string>
|
|
6501
7840
|
</Properties>
|
|
6502
7841
|
</Item>
|
|
6503
|
-
<Item class="ModuleScript" referent="
|
|
7842
|
+
<Item class="ModuleScript" referent="18">
|
|
6504
7843
|
<Properties>
|
|
6505
7844
|
<string name="Name">RuntimeLib</string>
|
|
6506
7845
|
<string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
|
|
@@ -6767,15 +8106,15 @@ return TS
|
|
|
6767
8106
|
</Properties>
|
|
6768
8107
|
</Item>
|
|
6769
8108
|
</Item>
|
|
6770
|
-
<Item class="Folder" referent="
|
|
8109
|
+
<Item class="Folder" referent="21">
|
|
6771
8110
|
<Properties>
|
|
6772
8111
|
<string name="Name">node_modules</string>
|
|
6773
8112
|
</Properties>
|
|
6774
|
-
<Item class="Folder" referent="
|
|
8113
|
+
<Item class="Folder" referent="22">
|
|
6775
8114
|
<Properties>
|
|
6776
8115
|
<string name="Name">@rbxts</string>
|
|
6777
8116
|
</Properties>
|
|
6778
|
-
<Item class="ModuleScript" referent="
|
|
8117
|
+
<Item class="ModuleScript" referent="19">
|
|
6779
8118
|
<Properties>
|
|
6780
8119
|
<string name="Name">services</string>
|
|
6781
8120
|
<string name="Source"><![CDATA[return setmetatable({}, {
|