· 8 years ago · Jun 28, 2018, 08:38 AM
1local Tool = script.Parent;
2
3function IsVersionOutdated(Version)
4 -- Returns whether the given version of Building Tools is out of date
5
6 -- Check most recent version number
7 local AssetInfo = Game:GetService('MarketplaceService'):GetProductInfo(142785488, Enum.InfoType.Asset);
8 local LatestMajorVersion, LatestMinorVersion, LatestPatchVersion = AssetInfo.Description:match '%[Version: ([0-9]+)%.([0-9]+)%.([0-9]+)%]';
9 local CurrentMajorVersion, CurrentMinorVersion, CurrentPatchVersion = Version:match '([0-9]+)%.([0-9]+)%.([0-9]+)';
10
11 -- Convert version data into numbers
12 local LatestMajorVersion, LatestMinorVersion, LatestPatchVersion =
13 tonumber(LatestMajorVersion), tonumber(LatestMinorVersion), tonumber(LatestPatchVersion);
14 local CurrentMajorVersion, CurrentMinorVersion, CurrentPatchVersion =
15 tonumber(CurrentMajorVersion), tonumber(CurrentMinorVersion), tonumber(CurrentPatchVersion);
16
17 -- Determine whether current version is outdated
18 if LatestMajorVersion > CurrentMajorVersion then
19 return true;
20 elseif LatestMajorVersion == CurrentMajorVersion then
21 if LatestMinorVersion > CurrentMinorVersion then
22 return true;
23 elseif LatestMinorVersion == CurrentMinorVersion then
24 return LatestPatchVersion > CurrentPatchVersion;
25 end;
26 end;
27
28 -- Return an up-to-date status if not oudated
29 return false;
30
31end;
32
33-- Ensure tool mode is enabled, auto-updating is enabled, and version is outdated
34if not (Tool:IsA 'Tool' and Tool.AutoUpdate.Value and IsVersionOutdated(Tool.Version.Value)) then
35 return;
36end;
37
38-- Use module to insert latest tool
39local GetLatestTool = require(580330877);
40if not GetLatestTool then
41 return;
42end;
43
44-- Get latest copy of tool
45local NewTool = GetLatestTool();
46if NewTool then
47
48 -- Prevent update attempt loops since fetched version is now cached
49 NewTool.AutoUpdate.Value = false;
50
51 -- Cancel replacing current tool if fetched version is the same
52 if NewTool.Version.Value == Tool.Version.Value then
53 return;
54 end;
55
56 -- Detach update script from tool and save old tool parent
57 script.Parent = nil;
58 local ToolParent = Tool.Parent;
59
60 -- Remove current tool (delayed to prevent parenting conflicts)
61 wait(0.05);
62 Tool.Parent = nil;
63
64 -- Remove the tool again if anything attempts to reparent it
65 Tool.Changed:connect(function (Property)
66 if Property == 'Parent' and Tool.Parent then
67 wait(0.05);
68 Tool.Parent = nil;
69 end;
70 end);
71
72 -- Add the new tool
73 NewTool.Parent = ToolParent;
74
75end;
76local Tool = script.Parent;
77
78function IsVersionOutdated(Version)
79 -- Returns whether the given version of Building Tools is out of date
80
81 -- Check most recent version number
82 local AssetInfo = Game:GetService('MarketplaceService'):GetProductInfo(142785488, Enum.InfoType.Asset);
83 local LatestMajorVersion, LatestMinorVersion, LatestPatchVersion = AssetInfo.Description:match '%[Version: ([0-9]+)%.([0-9]+)%.([0-9]+)%]';
84 local CurrentMajorVersion, CurrentMinorVersion, CurrentPatchVersion = Version:match '([0-9]+)%.([0-9]+)%.([0-9]+)';
85
86 -- Convert version data into numbers
87 local LatestMajorVersion, LatestMinorVersion, LatestPatchVersion =
88 tonumber(LatestMajorVersion), tonumber(LatestMinorVersion), tonumber(LatestPatchVersion);
89 local CurrentMajorVersion, CurrentMinorVersion, CurrentPatchVersion =
90 tonumber(CurrentMajorVersion), tonumber(CurrentMinorVersion), tonumber(CurrentPatchVersion);
91
92 -- Determine whether current version is outdated
93 if LatestMajorVersion > CurrentMajorVersion then
94 return true;
95 elseif LatestMajorVersion == CurrentMajorVersion then
96 if LatestMinorVersion > CurrentMinorVersion then
97 return true;
98 elseif LatestMinorVersion == CurrentMinorVersion then
99 return LatestPatchVersion > CurrentPatchVersion;
100 end;
101 end;
102
103 -- Return an up-to-date status if not oudated
104 return false;
105
106end;
107
108-- Ensure tool mode is enabled, auto-updating is enabled, and version is outdated
109if not (Tool:IsA 'Tool' and Tool.AutoUpdate.Value and IsVersionOutdated(Tool.Version.Value)) then
110 return;
111end;
112
113-- Use module to insert latest tool
114local GetLatestTool = require(580330877);
115if not GetLatestTool then
116 return;
117end;
118
119-- Get latest copy of tool
120local NewTool = GetLatestTool();
121if NewTool then
122
123 -- Prevent update attempt loops since fetched version is now cached
124 NewTool.AutoUpdate.Value = false;
125
126 -- Cancel replacing current tool if fetched version is the same
127 if NewTool.Version.Value == Tool.Version.Value then
128 return;
129 end;
130
131 -- Detach update script from tool and save old tool parent
132 script.Parent = nil;
133 local ToolParent = Tool.Parent;
134
135 -- Remove current tool (delayed to prevent parenting conflicts)
136 wait(0.05);
137 Tool.Parent = nil;
138
139 -- Remove the tool again if anything attempts to reparent it
140 Tool.Changed:connect(function (Property)
141 if Property == 'Parent' and Tool.Parent then
142 wait(0.05);
143 Tool.Parent = nil;
144 end;
145 end);
146
147 -- Add the new tool
148 NewTool.Parent = ToolParent;
149
150end;
151script.Parent.Value = Workspace.FilteringEnabled;
152local Count = script.Parent;
153local Tool = Count.Parent.Parent;
154local Support = require(Tool:WaitForChild 'SupportLibrary');
155
156-- Exclude counting autoremoving items (thumbnail and autoupdating script)
157local AutoremovingItemsCount = 5 + 1;
158
159-- Provide total count of all descendants
160Count.Value = Support.GetDescendantCount(Tool) - AutoremovingItemsCount;
161local Indicator = script.Parent;
162local Tool = Indicator.Parent;
163
164-- Libraries
165local Support = require(Tool:WaitForChild 'SupportLibrary');
166
167-- Get the total component count
168local TotalCount = (Indicator:WaitForChild 'ComponentCount').Value;
169
170-- Wait for tool to load completely
171while not (Support.GetDescendantCount(Tool) >= TotalCount) do
172 wait(0.1);
173end;
174
175-- Set load indicator to true upon load completion
176Indicator.Value = true;
177local Tool = script.Parent;
178local Plugin = plugin;
179
180-- Expose plugin if in plugin mode
181_G[Tool] = { Plugin = Plugin };
182
183-- Load tool completely before proceeding
184local Indicator = Tool:WaitForChild 'Loaded';
185while not Indicator.Value do
186 Indicator.Changed:wait();
187end;
188
189-- Initialize the core
190local Core = require(Tool:WaitForChild 'Core');
191
192-- Attach core tools
193require(Tool.Tools.CoreToolLoader);
194ocal NamePattern = '([A-Za-z0-9_-]+)/([A-Za-z0-9_-]+)';
195local VersionPattern = '[A-Za-z0-9_-]+/[A-Za-z0-9_-]+@([%^~]?)([0-9]+)%.([0-9]+)%.([0-9]+)';
196
197-- Initialize cached library list
198_G.BTLibraryList = _G.BTLibraryList or {};
199local LibraryList = _G.BTLibraryList;
200
201function GetLibrary(LibraryID)
202 -- Returns the requested library
203
204 -- Parse library ID
205 local Creator, Name = LibraryID:match(NamePattern);
206 local VersionRange, Major, Minor, Patch = LibraryID:match(VersionPattern);
207
208 -- Convert version data to numbers
209 local Major, Minor, Patch = tonumber(Major), tonumber(Minor), tonumber(Patch);
210
211 -- Validate version information
212 assert(VersionRange and (Major and Minor and Patch), 'Invalid version');
213
214 -- Ensure library ID was given
215 if not (Creator and Name) then
216 return;
217 end;
218
219 -- If no version provided, return latest
220 if not VersionRange then
221 for _, Library in ipairs(LibraryList) do
222 if (Library.Creator:lower() == Creator:lower()) and
223 (Library.Name:lower() == Name:lower()) then
224 return Library.Library;
225 end;
226 end;
227
228 -- If exact version provided, return that version
229 elseif VersionRange == '' then
230 for _, Library in ipairs(LibraryList) do
231 if (Library.Creator:lower() == Creator:lower()) and
232 (Library.Name:lower() == Name:lower()) and
233 (Library.Version.Major == Major) and
234 (Library.Version.Minor == Minor) and
235 (Library.Version.Patch == Patch) then
236 return Library.Library;
237 end;
238 end;
239
240 -- If minor version specified, return latest compatible patch version
241 elseif VersionRange == '~' then
242 for _, Library in ipairs(LibraryList) do
243 if (Library.Creator:lower() == Creator:lower()) and
244 (Library.Name:lower() == Name:lower()) and
245 (Library.Version.Major == Major) and
246 (Library.Version.Minor == Minor) then
247 return Library.Library;
248 end;
249 end;
250
251 -- If major version specified, return latest compatible minor or patch version
252 elseif VersionRange == '^' then
253 for _, Library in ipairs(LibraryList) do
254 if (Library.Creator:lower() == Creator:lower()) and
255 (Library.Name:lower() == Name:lower()) and
256 (Library.Version.Major == Major) then
257 return Library.Library;
258 end;
259 end;
260 end;
261
262end;
263
264function GetLibraries(...)
265 -- Returns the requested libraries by their IDs
266
267 local RequestedLibraries = { ... };
268 local FoundLibraries = {};
269
270 -- Get each library
271 for Index, LibraryID in ipairs(RequestedLibraries) do
272 FoundLibraries[Index] = GetLibrary(LibraryID);
273 end;
274
275 -- Return the found libraries
276 return unpack(FoundLibraries, 1, table.maxn(FoundLibraries));
277
278end;
279
280function RegisterLibrary(Metadata, Library)
281 -- Registers the given library with its metadata into the cache list
282
283 -- Validate metadata
284 assert(type(Metadata.Name) == 'string', 'Library name must be a string');
285 assert(type(Metadata.Creator) == 'string', 'Library creator must be a string');
286 assert(Metadata.Name:match('^[A-Za-z0-9_-]+$'), 'Library name contains invalid characters');
287 assert(Metadata.Creator:match('^[A-Za-z0-9_-]+$'), 'Library creator contains invalid characters');
288 assert(type(Metadata.Version) == 'table', 'Invalid library version data');
289 assert(type(Metadata.Version.Major) == 'number', 'Invalid library version data');
290 assert(type(Metadata.Version.Minor) == 'number', 'Invalid library version data');
291 assert(type(Metadata.Version.Patch) == 'number', 'Invalid library version data');
292
293 -- Structure metadata
294 local Metadata = {
295 Name = Metadata.Name,
296 Creator = Metadata.Creator,
297 Version = {
298 Major = Metadata.Version.Major,
299 Minor = Metadata.Version.Minor,
300 Patch = Metadata.Version.Patch
301 },
302 Library = Library
303 };
304
305 -- Insert the library and its metadata into the list
306 table.insert(LibraryList, Metadata);
307
308 -- Sort the list by version (from latest to earliest)
309 table.sort(LibraryList, function (A, B)
310
311 -- Sort by major version
312 if A.Version.Major > B.Version.Major then
313 return true;
314
315 -- Sort by minor version when major version is same
316 elseif A.Version.Major == B.Version.Major then
317 if A.Version.Minor > B.Version.Minor then
318 return true;
319
320 -- Sort by patch version when same major and minor version
321 elseif A.Version.Minor == B.Version.Minor then
322 return A.Version.Patch > B.Version.Patch;
323 else
324 return false;
325 end;
326
327 -- Sort A after B if earlier version
328 else
329 return false;
330 end;
331 end);
332
333end;
334
335-- Load tool completely before loading cached libraries
336local Tool = script.Parent;
337local Indicator = Tool:WaitForChild 'Loaded';
338while not Indicator.Value do
339 Indicator.Changed:wait();
340end;
341
342-- Populate library list with cached libraries
343for _, Library in pairs(script:GetChildren()) do
344 pcall(function () RegisterLibrary(require(Library.Metadata), require(Library)) end);
345end;
346
347-- Expose GetLibraries function
348_G.GetLibraries = GetLibraries;
349local SyncAPI = script.Parent;
350local Tool = SyncAPI.Parent;
351local ServerEndpoint = SyncAPI:WaitForChild 'ServerEndpoint';
352
353function IsFilterModeEnabled()
354 return (Tool:WaitForChild 'FilterMode').Value;
355end;
356
357-- Provide functionality to the local API endpoint instance
358SyncAPI.OnInvoke = function (...)
359
360 -- Route requests to server endpoint if in filter mode
361 if IsFilterModeEnabled() then
362 return ServerEndpoint:InvokeServer(...);
363
364 -- Perform requests locally if working locally
365 else
366 SyncModule = require(SyncAPI:WaitForChild 'SyncModule');
367 return SyncModule.PerformAction(Game.Players.LocalPlayer, ...);
368 end;
369
370end;
371local ServerEndpoint = script.Parent;
372local SyncAPI = ServerEndpoint.Parent;
373local Tool = SyncAPI.Parent;
374
375-- Start the server-side sync module
376SyncModule = require(SyncAPI:WaitForChild 'SyncModule');
377
378-- Provide functionality to the server API endpoint instance
379ServerEndpoint.OnServerInvoke = function (Client, ...)
380 return SyncModule.PerformAction(Client, ...);
381end;
382-- References
383SyncAPI = script.Parent;
384Tool = SyncAPI.Parent;
385Player = nil;
386
387-- Libraries
388RbxUtility = LoadLibrary 'RbxUtility';
389Support = require(Tool.SupportLibrary);
390Security = require(Tool.SecurityModule);
391RegionModule = require(Tool['Region by AxisAngle']);
392Serialization = require(Tool.SerializationModule);
393Create = RbxUtility.Create;
394CreateSignal = RbxUtility.CreateSignal;
395
396-- Import services
397Support.ImportServices();
398
399-- Default options
400Options = {
401 DefaultPartParent = Workspace
402};
403
404-- Keep track of created items in memory to not lose them in garbage collection
405CreatedInstances = {};
406LastParents = {};
407
408-- Determine whether we're in tool or plugin mode
409if Tool:IsA 'Tool' then
410 ToolMode = 'Tool';
411elseif Tool:IsA 'Model' then
412 ToolMode = 'Plugin';
413end;
414
415-- List of actions that could be requested
416Actions = {
417
418 ['RecolorHandle'] = function (NewColor)
419 -- Recolors the tool handle
420 Tool.Handle.BrickColor = NewColor;
421 end;
422
423 ['Clone'] = function (Parts)
424 -- Clones the given parts
425
426 -- Make sure the given items are all parts
427 if not ArePartsSelectable(Parts) then
428 return;
429 end;
430
431 -- Cache up permissions for all private areas
432 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
433
434 -- Make sure the player is allowed to perform changes to these parts
435 if Security.ArePartsViolatingAreas(Parts, Player, false, AreaPermissions) then
436 return;
437 end;
438
439 local Clones = {};
440
441 -- Clone the parts
442 for _, Part in pairs(Parts) do
443
444 -- Create the clone
445 local Clone = Part:Clone();
446 Clone.Parent = Options.DefaultPartParent;
447
448 -- Register the clone
449 table.insert(Clones, Clone);
450 CreatedInstances[Part] = Part;
451
452 end;
453
454 -- Return the clones
455 return Clones;
456 end;
457
458 ['CreatePart'] = function (PartType, Position)
459 -- Creates a new part based on `PartType`
460
461 -- Create the part
462 local NewPart = Support.CreatePart(PartType);
463 NewPart.TopSurface = Enum.SurfaceType.Smooth;
464 NewPart.BottomSurface = Enum.SurfaceType.Smooth;
465
466 -- Position the part
467 NewPart.CFrame = Position;
468
469 -- Cache up permissions for all private areas
470 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas({ NewPart }), Player);
471
472 -- Make sure the player is allowed to create parts in the area
473 if Security.ArePartsViolatingAreas({ NewPart }, Player, false, AreaPermissions) then
474 return;
475 end;
476
477 -- Parent the part
478 NewPart.Parent = Options.DefaultPartParent;
479
480 -- Register the part
481 CreatedInstances[NewPart] = NewPart;
482
483 -- Return the part
484 return NewPart;
485 end;
486
487 ['Remove'] = function (Objects)
488 -- Removes the given objects
489
490 -- Get the relevant parts for each object, for permission checking
491 local Parts = {};
492
493 -- Go through the selection
494 for _, Object in pairs(Objects) do
495
496 -- Make sure the object still exists
497 if Object then
498
499 if Object:IsA 'BasePart' then
500 table.insert(Parts, Object);
501
502 elseif Object:IsA 'Smoke' or Object:IsA 'Fire' or Object:IsA 'Sparkles' or Object:IsA 'DataModelMesh' or Object:IsA 'Decal' or Object:IsA 'Texture' or Object:IsA 'Light' then
503 table.insert(Parts, Object.Parent);
504 end;
505
506 end;
507
508 end;
509
510 -- Ensure relevant parts are selectable
511 if not ArePartsSelectable(Parts) then
512 return;
513 end;
514
515 -- Cache up permissions for all private areas
516 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
517
518 -- Make sure the player is allowed to perform changes to these parts
519 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
520 return;
521 end;
522
523 -- After confirming permissions, perform each removal
524 for _, Object in pairs(Objects) do
525
526 -- Store the part's current parent
527 LastParents[Object] = Object.Parent;
528
529 -- Register the object
530 CreatedInstances[Object] = Object;
531
532 -- Set the object's current parent to `nil`
533 Object.Parent = nil;
534
535 end;
536
537 end;
538
539 ['UndoRemove'] = function (Objects)
540 -- Restores the given removed objects to their last parents
541
542 -- Get the relevant parts for each object, for permission checking
543 local Parts = {};
544
545 -- Go through the selection
546 for _, Object in pairs(Objects) do
547
548 -- Make sure the object still exists, and that its last parent is registered
549 if Object and LastParents[Object] then
550
551 if Object:IsA 'BasePart' then
552 table.insert(Parts, Object);
553
554 elseif Object:IsA 'Smoke' or Object:IsA 'Fire' or Object:IsA 'Sparkles' or Object:IsA 'DataModelMesh' or Object:IsA 'Decal' or Object:IsA 'Texture' or Object:IsA 'Light' then
555 table.insert(Parts, Object.Parent);
556 end;
557
558 end;
559
560 end;
561
562 -- Ensure relevant parts are selectable
563 if not ArePartsSelectable(Parts) then
564 return;
565 end;
566
567 -- Cache up permissions for all private areas
568 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
569
570 -- Make sure the player is allowed to perform changes to these parts
571 if Security.ArePartsViolatingAreas(Parts, Player, false, AreaPermissions) then
572 return;
573 end;
574
575 -- After confirming permissions, perform each removal
576 for _, Object in pairs(Objects) do
577
578 -- Store the part's current parent
579 local LastParent = LastParents[Object];
580 LastParents[Object] = Object.Parent;
581
582 -- Register the object
583 CreatedInstances[Object] = Object;
584
585 -- Set the object's parent to the last parent
586 Object.Parent = LastParent;
587
588 end;
589
590 end;
591
592 ['SyncMove'] = function (Changes)
593 -- Updates parts server-side given their new CFrames
594
595 -- Grab a list of every part we're attempting to modify
596 local Parts = {};
597 for _, Change in pairs(Changes) do
598 if Change.Part then
599 table.insert(Parts, Change.Part);
600 end;
601 end;
602
603 -- Ensure parts are selectable
604 if not ArePartsSelectable(Parts) then
605 return;
606 end;
607
608 -- Cache up permissions for all private areas
609 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
610
611 -- Make sure the player is allowed to perform changes to these parts
612 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
613 return;
614 end;
615
616 -- Reorganize the changes
617 local ChangeSet = {};
618 for _, Change in pairs(Changes) do
619 if Change.Part then
620 Change.InitialState = { Anchored = Change.Part.Anchored, CFrame = Change.Part.CFrame };
621 ChangeSet[Change.Part] = Change;
622 end;
623 end;
624
625 -- Preserve joints
626 for Part, Change in pairs(ChangeSet) do
627 Change.Joints = PreserveJoints(Part, ChangeSet);
628 end;
629
630 -- Perform each change
631 for Part, Change in pairs(ChangeSet) do
632
633 -- Stabilize the parts and maintain the original anchor state
634 Part.Anchored = true;
635 Part:BreakJoints();
636 Part.Velocity = Vector3.new();
637 Part.RotVelocity = Vector3.new();
638
639 -- Set the part's CFrame
640 Part.CFrame = Change.CFrame;
641
642 end;
643
644 -- Make sure the player is authorized to move parts into this area
645 if Security.ArePartsViolatingAreas(Parts, Player, false, AreaPermissions) then
646
647 -- Revert changes if unauthorized destination
648 for Part, Change in pairs(ChangeSet) do
649 Part.CFrame = Change.InitialState.CFrame;
650 end;
651
652 end;
653
654 -- Restore the parts' original states
655 for Part, Change in pairs(ChangeSet) do
656 Part:MakeJoints();
657 RestoreJoints(Change.Joints);
658 Part.Anchored = Change.InitialState.Anchored;
659 end;
660
661 end;
662
663 ['SyncResize'] = function (Changes)
664 -- Updates parts server-side given their new sizes and CFrames
665
666 -- Grab a list of every part we're attempting to modify
667 local Parts = {};
668 for _, Change in pairs(Changes) do
669 if Change.Part then
670 table.insert(Parts, Change.Part);
671 end;
672 end;
673
674 -- Ensure parts are selectable
675 if not ArePartsSelectable(Parts) then
676 return;
677 end;
678
679 -- Cache up permissions for all private areas
680 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
681
682 -- Make sure the player is allowed to perform changes to these parts
683 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
684 return;
685 end;
686
687 -- Reorganize the changes
688 local ChangeSet = {};
689 for _, Change in pairs(Changes) do
690 if Change.Part then
691 Change.InitialState = { Anchored = Change.Part.Anchored, Size = Change.Part.Size, CFrame = Change.Part.CFrame };
692 ChangeSet[Change.Part] = Change;
693 end;
694 end;
695
696 -- Perform each change
697 for Part, Change in pairs(ChangeSet) do
698
699 -- Stabilize the parts and maintain the original anchor state
700 Part.Anchored = true;
701 Part:BreakJoints();
702 Part.Velocity = Vector3.new();
703 Part.RotVelocity = Vector3.new();
704
705 -- Set the part's size and CFrame
706 Part.Size = Change.Size;
707 Part.CFrame = Change.CFrame;
708
709 end;
710
711 -- Make sure the player is authorized to move parts into this area
712 if Security.ArePartsViolatingAreas(Parts, Player, false, AreaPermissions) then
713
714 -- Revert changes if unauthorized destination
715 for Part, Change in pairs(ChangeSet) do
716 Part.Size = Change.InitialState.Size;
717 Part.CFrame = Change.InitialState.CFrame;
718 end;
719
720 end;
721
722 -- Restore the parts' original states
723 for Part, Change in pairs(ChangeSet) do
724 Part:MakeJoints();
725 Part.Anchored = Change.InitialState.Anchored;
726 end;
727
728 end;
729
730 ['SyncRotate'] = function (Changes)
731 -- Updates parts server-side given their new CFrames
732
733 -- Grab a list of every part we're attempting to modify
734 local Parts = {};
735 for _, Change in pairs(Changes) do
736 if Change.Part then
737 table.insert(Parts, Change.Part);
738 end;
739 end;
740
741 -- Ensure parts are selectable
742 if not ArePartsSelectable(Parts) then
743 return;
744 end;
745
746 -- Cache up permissions for all private areas
747 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
748
749 -- Make sure the player is allowed to perform changes to these parts
750 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
751 return;
752 end;
753
754 -- Reorganize the changes
755 local ChangeSet = {};
756 for _, Change in pairs(Changes) do
757 if Change.Part then
758 Change.InitialState = { Anchored = Change.Part.Anchored, CFrame = Change.Part.CFrame };
759 ChangeSet[Change.Part] = Change;
760 end;
761 end;
762
763 -- Preserve joints
764 for Part, Change in pairs(ChangeSet) do
765 Change.Joints = PreserveJoints(Part, ChangeSet);
766 end;
767
768 -- Perform each change
769 for Part, Change in pairs(ChangeSet) do
770
771 -- Stabilize the parts and maintain the original anchor state
772 Part.Anchored = true;
773 Part:BreakJoints();
774 Part.Velocity = Vector3.new();
775 Part.RotVelocity = Vector3.new();
776
777 -- Set the part's CFrame
778 Part.CFrame = Change.CFrame;
779
780 end;
781
782 -- Make sure the player is authorized to move parts into this area
783 if Security.ArePartsViolatingAreas(Parts, Player, false, AreaPermissions) then
784
785 -- Revert changes if unauthorized destination
786 for Part, Change in pairs(ChangeSet) do
787 Part.CFrame = Change.InitialState.CFrame;
788 end;
789
790 end;
791
792 -- Restore the parts' original states
793 for Part, Change in pairs(ChangeSet) do
794 Part:MakeJoints();
795 RestoreJoints(Change.Joints);
796 Part.Anchored = Change.InitialState.Anchored;
797 end;
798
799 end;
800
801 ['SyncColor'] = function (Changes)
802 -- Updates parts server-side given their new colors
803
804 -- Grab a list of every part we're attempting to modify
805 local Parts = {};
806 for _, Change in pairs(Changes) do
807 if Change.Part then
808 table.insert(Parts, Change.Part);
809 end;
810 end;
811
812 -- Ensure parts are selectable
813 if not ArePartsSelectable(Parts) then
814 return;
815 end;
816
817 -- Cache up permissions for all private areas
818 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
819
820 -- Make sure the player is allowed to perform changes to these parts
821 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
822 return;
823 end;
824
825 -- Reorganize the changes
826 local ChangeSet = {};
827 for _, Change in pairs(Changes) do
828 if Change.Part then
829 ChangeSet[Change.Part] = Change;
830 end;
831 end;
832
833 -- Perform each change
834 for Part, Change in pairs(ChangeSet) do
835
836 -- Set the part's color
837 Part.Color = Change.Color;
838
839 -- If this part is a union, set its UsePartColor state
840 if Part.ClassName == 'UnionOperation' then
841 Part.UsePartColor = Change.UnionColoring;
842 end;
843
844 end;
845
846 end;
847
848 ['SyncSurface'] = function (Changes)
849 -- Updates parts server-side given their new surfaces
850
851 -- Grab a list of every part we're attempting to modify
852 local Parts = {};
853 for _, Change in pairs(Changes) do
854 if Change.Part then
855 table.insert(Parts, Change.Part);
856 end;
857 end;
858
859 -- Ensure parts are selectable
860 if not ArePartsSelectable(Parts) then
861 return;
862 end;
863
864 -- Cache up permissions for all private areas
865 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
866
867 -- Make sure the player is allowed to perform changes to these parts
868 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
869 return;
870 end;
871
872 -- Reorganize the changes
873 local ChangeSet = {};
874 for _, Change in pairs(Changes) do
875 if Change.Part then
876 ChangeSet[Change.Part] = Change;
877 end;
878 end;
879
880 -- Perform each change
881 for Part, Change in pairs(ChangeSet) do
882
883 -- Apply each surface change
884 for Surface, SurfaceType in pairs(Change.Surfaces) do
885 Part[Surface .. 'Surface'] = SurfaceType;
886 end;
887
888 end;
889
890 end;
891
892 ['CreateLights'] = function (Changes)
893 -- Creates lights in the given parts
894
895 -- Grab a list of every part we're attempting to modify
896 local Parts = {};
897 for _, Change in pairs(Changes) do
898 if Change.Part then
899 table.insert(Parts, Change.Part);
900 end;
901 end;
902
903 -- Ensure parts are selectable
904 if not ArePartsSelectable(Parts) then
905 return;
906 end;
907
908 -- Cache up permissions for all private areas
909 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
910
911 -- Make sure the player is allowed to perform changes to these parts
912 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
913 return;
914 end;
915
916 -- Reorganize the changes
917 local ChangeSet = {};
918 for _, Change in pairs(Changes) do
919 if Change.Part then
920 ChangeSet[Change.Part] = Change;
921 end;
922 end;
923
924 -- Make a list of allowed light type requests
925 local AllowedLightTypes = { PointLight = true, SurfaceLight = true, SpotLight = true };
926
927 -- Keep track of the newly created lights
928 local Lights = {};
929
930 -- Create each light
931 for Part, Change in pairs(ChangeSet) do
932
933 -- Make sure the requested light type is valid
934 if AllowedLightTypes[Change.LightType] then
935
936 -- Create the light
937 local Light = Instance.new(Change.LightType, Part);
938 table.insert(Lights, Light);
939
940 -- Register the light
941 CreatedInstances[Light] = Light;
942
943 end;
944
945 end;
946
947 -- Return the new lights
948 return Lights;
949
950 end;
951
952 ['SyncLighting'] = function (Changes)
953 -- Updates aspects of the given selection's lights
954
955 -- Grab a list of every part we're attempting to modify
956 local Parts = {};
957 for _, Change in pairs(Changes) do
958 if Change.Part then
959 table.insert(Parts, Change.Part);
960 end;
961 end;
962
963 -- Ensure parts are selectable
964 if not ArePartsSelectable(Parts) then
965 return;
966 end;
967
968 -- Cache up permissions for all private areas
969 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
970
971 -- Make sure the player is allowed to perform changes to these parts
972 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
973 return;
974 end;
975
976 -- Reorganize the changes
977 local ChangeSet = {};
978 for _, Change in pairs(Changes) do
979 if Change.Part then
980 ChangeSet[Change.Part] = Change;
981 end;
982 end;
983
984 -- Make a list of allowed light type requests
985 local AllowedLightTypes = { PointLight = true, SurfaceLight = true, SpotLight = true };
986
987 -- Update each part's lights
988 for Part, Change in pairs(ChangeSet) do
989
990 -- Make sure that the light type requested is valid
991 if AllowedLightTypes[Change.LightType] then
992
993 -- Grab the part's light
994 local Light = Support.GetChildOfClass(Part, Change.LightType);
995
996 -- Make sure the light exists
997 if Light then
998
999 -- Make the requested changes
1000 if Change.Range ~= nil then
1001 Light.Range = Change.Range;
1002 end;
1003 if Change.Brightness ~= nil then
1004 Light.Brightness = Change.Brightness;
1005 end;
1006 if Change.Color ~= nil then
1007 Light.Color = Change.Color;
1008 end;
1009 if Change.Shadows ~= nil then
1010 Light.Shadows = Change.Shadows;
1011 end;
1012 if Change.Face ~= nil then
1013 Light.Face = Change.Face;
1014 end;
1015 if Change.Angle ~= nil then
1016 Light.Angle = Change.Angle;
1017 end;
1018
1019 end;
1020
1021 end;
1022
1023 end;
1024
1025 end;
1026
1027 ['CreateDecorations'] = function (Changes)
1028 -- Creates decorations in the given parts
1029
1030 -- Grab a list of every part we're attempting to modify
1031 local Parts = {};
1032 for _, Change in pairs(Changes) do
1033 if Change.Part then
1034 table.insert(Parts, Change.Part);
1035 end;
1036 end;
1037
1038 -- Ensure parts are selectable
1039 if not ArePartsSelectable(Parts) then
1040 return;
1041 end;
1042
1043 -- Cache up permissions for all private areas
1044 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1045
1046 -- Make sure the player is allowed to perform changes to these parts
1047 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1048 return;
1049 end;
1050
1051 -- Reorganize the changes
1052 local ChangeSet = {};
1053 for _, Change in pairs(Changes) do
1054 if Change.Part then
1055 ChangeSet[Change.Part] = Change;
1056 end;
1057 end;
1058
1059 -- Make a list of allowed decoration type requests
1060 local AllowedDecorationTypes = { Smoke = true, Fire = true, Sparkles = true };
1061
1062 -- Keep track of the newly created decorations
1063 local Decorations = {};
1064
1065 -- Create each decoration
1066 for Part, Change in pairs(ChangeSet) do
1067
1068 -- Make sure the requested decoration type is valid
1069 if AllowedDecorationTypes[Change.DecorationType] then
1070
1071 -- Create the decoration
1072 local Decoration = Instance.new(Change.DecorationType, Part);
1073 table.insert(Decorations, Decoration);
1074
1075 -- Register the decoration
1076 CreatedInstances[Decoration] = Decoration;
1077
1078 end;
1079
1080 end;
1081
1082 -- Return the new decorations
1083 return Decorations;
1084
1085 end;
1086
1087 ['SyncDecorate'] = function (Changes)
1088 -- Updates aspects of the given selection's decorations
1089
1090 -- Grab a list of every part we're attempting to modify
1091 local Parts = {};
1092 for _, Change in pairs(Changes) do
1093 if Change.Part then
1094 table.insert(Parts, Change.Part);
1095 end;
1096 end;
1097
1098 -- Ensure parts are selectable
1099 if not ArePartsSelectable(Parts) then
1100 return;
1101 end;
1102
1103 -- Cache up permissions for all private areas
1104 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1105
1106 -- Make sure the player is allowed to perform changes to these parts
1107 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1108 return;
1109 end;
1110
1111 -- Reorganize the changes
1112 local ChangeSet = {};
1113 for _, Change in pairs(Changes) do
1114 if Change.Part then
1115 ChangeSet[Change.Part] = Change;
1116 end;
1117 end;
1118
1119 -- Make a list of allowed decoration type requests
1120 local AllowedDecorationTypes = { Smoke = true, Fire = true, Sparkles = true };
1121
1122 -- Update each part's decorations
1123 for Part, Change in pairs(ChangeSet) do
1124
1125 -- Make sure that the decoration type requested is valid
1126 if AllowedDecorationTypes[Change.DecorationType] then
1127
1128 -- Grab the part's decoration
1129 local Decoration = Support.GetChildOfClass(Part, Change.DecorationType);
1130
1131 -- Make sure the decoration exists
1132 if Decoration then
1133
1134 -- Make the requested changes
1135 if Change.Color ~= nil then
1136 Decoration.Color = Change.Color;
1137 end;
1138 if Change.Opacity ~= nil then
1139 Decoration.Opacity = Change.Opacity;
1140 end;
1141 if Change.RiseVelocity ~= nil then
1142 Decoration.RiseVelocity = Change.RiseVelocity;
1143 end;
1144 if Change.Size ~= nil then
1145 Decoration.Size = Change.Size;
1146 end;
1147 if Change.Heat ~= nil then
1148 Decoration.Heat = Change.Heat;
1149 end;
1150 if Change.SecondaryColor ~= nil then
1151 Decoration.SecondaryColor = Change.SecondaryColor;
1152 end;
1153 if Change.SparkleColor ~= nil then
1154 Decoration.SparkleColor = Change.SparkleColor;
1155 end;
1156
1157 end;
1158
1159 end;
1160
1161 end;
1162
1163 end;
1164
1165 ['CreateMeshes'] = function (Changes)
1166 -- Creates meshes in the given parts
1167
1168 -- Grab a list of every part we're attempting to modify
1169 local Parts = {};
1170 for _, Change in pairs(Changes) do
1171 if Change.Part then
1172 table.insert(Parts, Change.Part);
1173 end;
1174 end;
1175
1176 -- Ensure parts are selectable
1177 if not ArePartsSelectable(Parts) then
1178 return;
1179 end;
1180
1181 -- Cache up permissions for all private areas
1182 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1183
1184 -- Make sure the player is allowed to perform changes to these parts
1185 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1186 return;
1187 end;
1188
1189 -- Reorganize the changes
1190 local ChangeSet = {};
1191 for _, Change in pairs(Changes) do
1192 if Change.Part then
1193 ChangeSet[Change.Part] = Change;
1194 end;
1195 end;
1196
1197 -- Keep track of the newly created meshes
1198 local Meshes = {};
1199
1200 -- Create each mesh
1201 for Part, Change in pairs(ChangeSet) do
1202
1203 -- Create the mesh
1204 local Mesh = Instance.new('SpecialMesh', Part);
1205 table.insert(Meshes, Mesh);
1206
1207 -- Register the mesh
1208 CreatedInstances[Mesh] = Mesh;
1209
1210 end;
1211
1212 -- Return the new meshes
1213 return Meshes;
1214
1215 end;
1216
1217 ['SyncMesh'] = function (Changes)
1218 -- Updates aspects of the given selection's meshes
1219
1220 -- Grab a list of every part we're attempting to modify
1221 local Parts = {};
1222 for _, Change in pairs(Changes) do
1223 if Change.Part then
1224 table.insert(Parts, Change.Part);
1225 end;
1226 end;
1227
1228 -- Ensure parts are selectable
1229 if not ArePartsSelectable(Parts) then
1230 return;
1231 end;
1232
1233 -- Cache up permissions for all private areas
1234 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1235
1236 -- Make sure the player is allowed to perform changes to these parts
1237 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1238 return;
1239 end;
1240
1241 -- Reorganize the changes
1242 local ChangeSet = {};
1243 for _, Change in pairs(Changes) do
1244 if Change.Part then
1245 ChangeSet[Change.Part] = Change;
1246 end;
1247 end;
1248
1249 -- Update each part's meshes
1250 for Part, Change in pairs(ChangeSet) do
1251
1252 -- Grab the part's mesh
1253 local Mesh = Support.GetChildOfClass(Part, 'SpecialMesh');
1254
1255 -- Make sure the mesh exists
1256 if Mesh then
1257
1258 -- Make the requested changes
1259 if Change.VertexColor ~= nil then
1260 Mesh.VertexColor = Change.VertexColor;
1261 end;
1262 if Change.MeshType ~= nil then
1263 Mesh.MeshType = Change.MeshType;
1264 end;
1265 if Change.Scale ~= nil then
1266 Mesh.Scale = Change.Scale;
1267 end;
1268 if Change.Offset ~= nil then
1269 Mesh.Offset = Change.Offset;
1270 end;
1271 if Change.MeshId ~= nil then
1272 Mesh.MeshId = Change.MeshId;
1273 end;
1274 if Change.TextureId ~= nil then
1275 Mesh.TextureId = Change.TextureId;
1276 end;
1277
1278 end;
1279
1280 end;
1281
1282 end;
1283
1284 ['CreateTextures'] = function (Changes)
1285 -- Creates textures in the given parts
1286
1287 -- Grab a list of every part we're attempting to modify
1288 local Parts = {};
1289 for _, Change in pairs(Changes) do
1290 if Change.Part then
1291 table.insert(Parts, Change.Part);
1292 end;
1293 end;
1294
1295 -- Ensure parts are selectable
1296 if not ArePartsSelectable(Parts) then
1297 return;
1298 end;
1299
1300 -- Cache up permissions for all private areas
1301 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1302
1303 -- Make sure the player is allowed to perform changes to these parts
1304 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1305 return;
1306 end;
1307
1308 -- Reorganize the changes
1309 local ChangeSet = {};
1310 for _, Change in pairs(Changes) do
1311 if Change.Part then
1312 ChangeSet[Change.Part] = Change;
1313 end;
1314 end;
1315
1316 -- Make a list of allowed texture type requests
1317 local AllowedTextureTypes = { Texture = true, Decal = true };
1318
1319 -- Keep track of the newly created textures
1320 local Textures = {};
1321
1322 -- Create each texture
1323 for Part, Change in pairs(ChangeSet) do
1324
1325 -- Make sure the requested light type is valid
1326 if AllowedTextureTypes[Change.TextureType] then
1327
1328 -- Create the texture
1329 local Texture = Instance.new(Change.TextureType, Part);
1330 Texture.Face = Change.Face;
1331 table.insert(Textures, Texture);
1332
1333 -- Register the texture
1334 CreatedInstances[Texture] = Texture;
1335
1336 end;
1337
1338 end;
1339
1340 -- Return the new textures
1341 return Textures;
1342
1343 end;
1344
1345 ['SyncTexture'] = function (Changes)
1346 -- Updates aspects of the given selection's textures
1347
1348 -- Grab a list of every part we're attempting to modify
1349 local Parts = {};
1350 for _, Change in pairs(Changes) do
1351 if Change.Part then
1352 table.insert(Parts, Change.Part);
1353 end;
1354 end;
1355
1356 -- Ensure parts are selectable
1357 if not ArePartsSelectable(Parts) then
1358 return;
1359 end;
1360
1361 -- Cache up permissions for all private areas
1362 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1363
1364 -- Make sure the player is allowed to perform changes to these parts
1365 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1366 return;
1367 end;
1368
1369 -- Reorganize the changes
1370 local ChangeSet = {};
1371 for _, Change in pairs(Changes) do
1372 if Change.Part then
1373 ChangeSet[Change.Part] = Change;
1374 end;
1375 end;
1376
1377 -- Make a list of allowed texture type requests
1378 local AllowedTextureTypes = { Texture = true, Decal = true };
1379
1380 -- Update each part's textures
1381 for Part, Change in pairs(ChangeSet) do
1382
1383 -- Make sure that the texture type requested is valid
1384 if AllowedTextureTypes[Change.TextureType] then
1385
1386 -- Get the right textures within the part
1387 for _, Texture in pairs(Part:GetChildren()) do
1388 if Texture.ClassName == Change.TextureType and Texture.Face == Change.Face then
1389
1390 -- Perform the changes
1391 if Change.Texture ~= nil then
1392 Texture.Texture = Change.Texture;
1393 end;
1394 if Change.Transparency ~= nil then
1395 Texture.Transparency = Change.Transparency;
1396 end;
1397 if Change.StudsPerTileU ~= nil then
1398 Texture.StudsPerTileU = Change.StudsPerTileU;
1399 end;
1400 if Change.StudsPerTileV ~= nil then
1401 Texture.StudsPerTileV = Change.StudsPerTileV;
1402 end;
1403
1404 end;
1405 end;
1406
1407 end;
1408
1409 end;
1410
1411 end;
1412
1413 ['SyncAnchor'] = function (Changes)
1414 -- Updates parts server-side given their new anchor status
1415
1416 -- Grab a list of every part we're attempting to modify
1417 local Parts = {};
1418 for _, Change in pairs(Changes) do
1419 if Change.Part then
1420 table.insert(Parts, Change.Part);
1421 end;
1422 end;
1423
1424 -- Ensure parts are selectable
1425 if not ArePartsSelectable(Parts) then
1426 return;
1427 end;
1428
1429 -- Cache up permissions for all private areas
1430 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1431
1432 -- Make sure the player is allowed to perform changes to these parts
1433 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1434 return;
1435 end;
1436
1437 -- Reorganize the changes
1438 local ChangeSet = {};
1439 for _, Change in pairs(Changes) do
1440 if Change.Part then
1441 ChangeSet[Change.Part] = Change;
1442 end;
1443 end;
1444
1445 -- Perform each change
1446 for Part, Change in pairs(ChangeSet) do
1447 Part.Anchored = Change.Anchored;
1448 end;
1449
1450 end;
1451
1452 ['SyncCollision'] = function (Changes)
1453 -- Updates parts server-side given their new collision status
1454
1455 -- Grab a list of every part we're attempting to modify
1456 local Parts = {};
1457 for _, Change in pairs(Changes) do
1458 if Change.Part then
1459 table.insert(Parts, Change.Part);
1460 end;
1461 end;
1462
1463 -- Ensure parts are selectable
1464 if not ArePartsSelectable(Parts) then
1465 return;
1466 end;
1467
1468 -- Cache up permissions for all private areas
1469 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1470
1471 -- Make sure the player is allowed to perform changes to these parts
1472 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1473 return;
1474 end;
1475
1476 -- Reorganize the changes
1477 local ChangeSet = {};
1478 for _, Change in pairs(Changes) do
1479 if Change.Part then
1480 ChangeSet[Change.Part] = Change;
1481 end;
1482 end;
1483
1484 -- Perform each change
1485 for Part, Change in pairs(ChangeSet) do
1486 Part.CanCollide = Change.CanCollide;
1487 end;
1488
1489 end;
1490
1491 ['SyncMaterial'] = function (Changes)
1492 -- Updates parts server-side given their new material
1493
1494 -- Grab a list of every part we're attempting to modify
1495 local Parts = {};
1496 for _, Change in pairs(Changes) do
1497 if Change.Part then
1498 table.insert(Parts, Change.Part);
1499 end;
1500 end;
1501
1502 -- Ensure parts are selectable
1503 if not ArePartsSelectable(Parts) then
1504 return;
1505 end;
1506
1507 -- Cache up permissions for all private areas
1508 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1509
1510 -- Make sure the player is allowed to perform changes to these parts
1511 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1512 return;
1513 end;
1514
1515 -- Reorganize the changes
1516 local ChangeSet = {};
1517 for _, Change in pairs(Changes) do
1518 if Change.Part then
1519 ChangeSet[Change.Part] = Change;
1520 end;
1521 end;
1522
1523 -- Perform each change
1524 for Part, Change in pairs(ChangeSet) do
1525 if Change.Material ~= nil then
1526 Part.Material = Change.Material;
1527 end;
1528 if Change.Transparency ~= nil then
1529 Part.Transparency = Change.Transparency;
1530 end;
1531 if Change.Reflectance ~= nil then
1532 Part.Reflectance = Change.Reflectance;
1533 end;
1534 end;
1535
1536 end;
1537
1538 ['CreateWelds'] = function (Parts, TargetPart)
1539 -- Creates welds for the given parts to the target part
1540
1541 -- Ensure parts are selectable
1542 if not ArePartsSelectable(Parts) then
1543 return;
1544 end;
1545
1546 -- Cache up permissions for all private areas
1547 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1548
1549 -- Make sure the player is allowed to perform changes to these parts
1550 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1551 return;
1552 end;
1553
1554 local Welds = {};
1555
1556 -- Create the welds
1557 for _, Part in pairs(Parts) do
1558
1559 -- Make sure we're not welding this part to itself
1560 if Part ~= TargetPart then
1561
1562 -- Calculate the offset of the part from the target part
1563 local Offset = Part.CFrame:toObjectSpace(TargetPart.CFrame);
1564
1565 -- Create the weld
1566 local Weld = Instance.new('Weld');
1567 Weld.Name = 'BTWeld';
1568 Weld.Part0 = TargetPart;
1569 Weld.Part1 = Part;
1570 Weld.C1 = Offset;
1571 Weld.Archivable = true;
1572 Weld.Parent = TargetPart;
1573
1574 -- Register the weld
1575 CreatedInstances[Weld] = Weld;
1576 table.insert(Welds, Weld);
1577
1578 end;
1579
1580 end;
1581
1582 -- Return the welds created
1583 return Welds;
1584 end;
1585
1586 ['RemoveWelds'] = function (Welds)
1587 -- Removes the given welds
1588
1589 local Parts = {};
1590
1591 -- Go through each weld
1592 for _, Weld in pairs(Welds) do
1593
1594 -- Make sure each given weld is valid
1595 if Weld.ClassName ~= 'Weld' then
1596 return;
1597 end;
1598
1599 -- Collect the relevant parts for this weld
1600 table.insert(Parts, Weld.Part0);
1601 table.insert(Parts, Weld.Part1);
1602
1603 end;
1604
1605 -- Ensure parts are selectable
1606 if not ArePartsSelectable(Parts) then
1607 return;
1608 end;
1609
1610 local WeldsRemoved = 0;
1611
1612 -- Cache up permissions for all private areas
1613 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1614
1615 -- Go through each weld
1616 for _, Weld in pairs(Welds) do
1617
1618 -- Check the permissions on each weld-related part
1619 local Part0Unauthorized = Security.ArePartsViolatingAreas({ Weld.Part0 }, Player, true, AreaPermissions);
1620 local Part1Unauthorized = Security.ArePartsViolatingAreas({ Weld.Part1 }, Player, true, AreaPermissions);
1621
1622 -- If at least one of the involved parts is authorized, remove the weld
1623 if not Part0Unauthorized or not Part1Unauthorized then
1624
1625 -- Register the weld
1626 CreatedInstances[Weld] = Weld;
1627 LastParents[Weld] = Weld.Parent;
1628 WeldsRemoved = WeldsRemoved + 1;
1629
1630 -- Remove the weld
1631 Weld.Parent = nil;
1632
1633 end;
1634
1635 end;
1636
1637 -- Return the number of welds removed
1638 return WeldsRemoved;
1639 end;
1640
1641 ['UndoRemovedWelds'] = function (Welds)
1642 -- Restores the given removed welds
1643
1644 local Parts = {};
1645
1646 -- Go through each weld
1647 for _, Weld in pairs(Welds) do
1648
1649 -- Make sure each given weld is valid
1650 if Weld.ClassName ~= 'Weld' then
1651 return;
1652 end;
1653
1654 -- Make sure each weld has its old parent registered
1655 if not LastParents[Weld] then
1656 return;
1657 end;
1658
1659 -- Collect the relevant parts for this weld
1660 table.insert(Parts, Weld.Part0);
1661 table.insert(Parts, Weld.Part1);
1662
1663 end;
1664
1665 -- Ensure parts are selectable
1666 if not ArePartsSelectable(Parts) then
1667 return;
1668 end;
1669
1670 -- Cache up permissions for all private areas
1671 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1672
1673 -- Go through each weld
1674 for _, Weld in pairs(Welds) do
1675
1676 -- Check the permissions on each weld-related part
1677 local Part0Unauthorized = Security.ArePartsViolatingAreas({ Weld.Part0 }, Player, false, AreaPermissions);
1678 local Part1Unauthorized = Security.ArePartsViolatingAreas({ Weld.Part0 }, Player, false, AreaPermissions);
1679
1680 -- If at least one of the involved parts is authorized, restore the weld
1681 if not Part0Unauthorized or not Part1Unauthorized then
1682
1683 -- Store the part's current parent
1684 local LastParent = LastParents[Weld];
1685 LastParents[Weld] = Weld.Parent;
1686
1687 -- Register the weld
1688 CreatedInstances[Weld] = Weld;
1689
1690 -- Set the weld's parent to the last parent
1691 Weld.Parent = LastParent;
1692
1693 end;
1694
1695 end;
1696
1697 end;
1698
1699 ['Export'] = function (Parts)
1700 -- Serializes, exports, and returns ID for importing given parts
1701
1702 -- Offload action to server-side if API is running locally
1703 if RunService:IsClient() and not RunService:IsStudio() then
1704 return SyncAPI.ServerEndpoint:InvokeServer('Export', Parts);
1705 end;
1706
1707 -- Ensure valid selection
1708 assert(type(Parts) == 'table', 'Invalid item table');
1709
1710 -- Ensure there are items to export
1711 if #Parts == 0 then
1712 return;
1713 end;
1714
1715 -- Ensure parts are selectable
1716 if not ArePartsSelectable(Parts) then
1717 return;
1718 end;
1719
1720 -- Cache up permissions for all private areas
1721 local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Parts), Player);
1722
1723 -- Make sure the player is allowed to access these parts
1724 if Security.ArePartsViolatingAreas(Parts, Player, true, AreaPermissions) then
1725 return;
1726 end;
1727
1728 -- Get all descendants of the parts
1729 local Items = Support.CloneTable(Parts);
1730 for _, Part in pairs(Parts) do
1731 Support.ConcatTable(Items, Support.GetAllDescendants(Part));
1732 end;
1733
1734 -- After confirming permissions, serialize parts
1735 local SerializedBuildData = Serialization.SerializeModel(Items);
1736
1737 -- Push serialized data to server
1738 local Response = HttpService:JSONDecode(
1739 HttpService:PostAsync(
1740 'http://f3xteam.com/bt/export',
1741 HttpService:JSONEncode { data = SerializedBuildData, version = 3, userId = (Player and Player.UserId) },
1742 Enum.HttpContentType.ApplicationJson,
1743 true
1744 )
1745 );
1746
1747 -- Return creation ID on success
1748 if Response.success then
1749 return Response.id;
1750 else
1751 error('Export failed due to server-side error', 2);
1752 end;
1753
1754 end;
1755
1756 ['IsHttpServiceEnabled'] = function ()
1757 -- Returns whether HttpService is enabled
1758
1759 -- Offload action to server-side if API is running locally
1760 if RunService:IsClient() and not RunService:IsStudio() then
1761 return SyncAPI.ServerEndpoint:InvokeServer('IsHttpServiceEnabled');
1762 end;
1763
1764 -- For in-game tool, return cached status if available
1765 if ToolMode == 'Tool' and (IsHttpServiceEnabled ~= nil) then
1766 return IsHttpServiceEnabled;
1767 end;
1768
1769 -- Perform test HTTP request
1770 local Success, Error = pcall(function ()
1771 return HttpService:GetAsync('http://google.com');
1772 end);
1773
1774 -- Determine whether HttpService is enabled
1775 if not Success and Error:match 'Http requests are not enabled' then
1776 IsHttpServiceEnabled = false;
1777 elseif Success then
1778 IsHttpServiceEnabled = true;
1779 end;
1780
1781 -- Return HttpService status
1782 return IsHttpServiceEnabled;
1783
1784 end;
1785
1786 ['ExtractMeshFromAsset'] = function (AssetId)
1787 -- Returns the first found mesh in the given asset
1788
1789 -- Offload action to server-side if API is running locally
1790 if RunService:IsClient() and not RunService:IsStudio() then
1791 return SyncAPI.ServerEndpoint:InvokeServer('ExtractMeshFromAsset', AssetId);
1792 end;
1793
1794 -- Ensure valid asset ID is given
1795 assert(type(AssetId) == 'number', 'Invalid asset ID');
1796
1797 -- Return parsed response from API
1798 return HttpService:JSONDecode(
1799 HttpService:GetAsync('http://f3xteam.com/bt/getFirstMeshData/' .. AssetId)
1800 );
1801
1802 end;
1803
1804 ['ExtractImageFromDecal'] = function (DecalAssetId)
1805 -- Returns the first image found in the given decal asset
1806
1807 -- Offload action to server-side if API is running locally
1808 if RunService:IsClient() and not RunService:IsStudio() then
1809 return SyncAPI.ServerEndpoint:InvokeServer('ExtractImageFromDecal', DecalAssetId);
1810 end;
1811
1812 -- Return direct response from the API
1813 return HttpService:GetAsync('http://f3xteam.com/bt/getDecalImageID/' .. DecalAssetId);
1814
1815 end;
1816
1817 ['SetMouseLockEnabled'] = function (Enabled)
1818 -- Sets whether mouse lock is enabled for the current player
1819
1820 -- Offload action to server-side if API is running locally
1821 if RunService:IsClient() and not RunService:IsStudio() then
1822 return SyncAPI.ServerEndpoint:InvokeServer('SetMouseLockEnabled', Enabled);
1823 end;
1824
1825 -- Set whether mouse lock is enabled
1826 Player.DevEnableMouseLock = Enabled;
1827
1828 end;
1829
1830};
1831
1832function ArePartsSelectable(Parts)
1833 -- Returns whether the parts are selectable
1834
1835 -- Check whether each part is selectable
1836 for _, Part in pairs(Parts) do
1837 if not Part:IsA 'BasePart' or Part.Locked then
1838 return false;
1839 end;
1840 end;
1841
1842 -- Return true if all parts are selectable
1843 return true;
1844
1845end;
1846
1847-- References to reduce indexing time
1848local GetConnectedParts = Instance.new('Part').GetConnectedParts;
1849local GetChildren = script.GetChildren;
1850
1851function GetPartJoints(Part, Whitelist)
1852 -- Returns any manual joints involving `Part`
1853
1854 local Joints = {};
1855
1856 -- Get joints stored inside `Part`
1857 for Joint, JointParent in pairs(SearchJoints(Part, Part, Whitelist)) do
1858 Joints[Joint] = JointParent;
1859 end;
1860
1861 -- Get joints stored inside connected parts
1862 for _, ConnectedPart in pairs(GetConnectedParts(Part)) do
1863 for Joint, JointParent in pairs(SearchJoints(ConnectedPart, Part, Whitelist)) do
1864 Joints[Joint] = JointParent;
1865 end;
1866 end;
1867
1868 -- Return all found joints
1869 return Joints;
1870
1871end;
1872
1873-- Types of joints to assume should be preserved
1874local ManualJointTypes = Support.FlipTable { 'Weld', 'ManualWeld', 'ManualGlue', 'Motor', 'Motor6D' };
1875
1876function SearchJoints(Haystack, Part, Whitelist)
1877 -- Searches for and returns manual joints in `Haystack` involving `Part` and other parts in `Whitelist`
1878
1879 local Joints = {};
1880
1881 -- Search the haystack for joints involving `Part`
1882 for _, Item in pairs(GetChildren(Haystack)) do
1883
1884 -- Check if this item is a manual, intentional joint
1885 if ManualJointTypes[Item.ClassName] and
1886 (Whitelist[Item.Part0] and Whitelist[Item.Part1]) then
1887
1888 -- Save joint and state if intentional
1889 Joints[Item] = Item.Parent;
1890
1891 end;
1892
1893 end;
1894
1895 -- Return the found joints
1896 return Joints;
1897
1898end;
1899
1900function RestoreJoints(Joints)
1901 -- Restores the joints from the given `Joints` data
1902
1903 -- Restore each joint
1904 for Joint, JointParent in pairs(Joints) do
1905 Joint.Parent = JointParent;
1906 end;
1907
1908end;
1909
1910function PreserveJoints(Part, Whitelist)
1911 -- Preserves and returns intentional joints of `Part` connecting parts in `Whitelist`
1912
1913 -- Get the part's joints
1914 local Joints = GetPartJoints(Part, Whitelist);
1915
1916 -- Save the joints from being broken
1917 for Joint in pairs(Joints) do
1918 Joint.Parent = nil;
1919 end;
1920
1921 -- Return the joints
1922 return Joints;
1923
1924end;
1925
1926-- Keep current player updated in tool mode
1927if ToolMode == 'Tool' then
1928
1929 -- Set current player if in backpack
1930 if Tool.Parent and Tool.Parent:IsA 'Backpack' then
1931 Player = Tool.Parent.Parent;
1932
1933 -- Set current player if in character
1934 elseif Tool.Parent and Tool.Parent:IsA 'Model' then
1935 Player = Players:GetPlayerFromCharacter(Tool.Parent);
1936
1937 -- Clear `Player` if not in possession of a player
1938 else
1939 Player = nil;
1940 end;
1941
1942 -- Stay updated with latest player operating the tool
1943 Tool.AncestryChanged:Connect(function (Child, Parent)
1944
1945 -- Ensure tool's parent changed
1946 if Child ~= Tool then
1947 return;
1948 end;
1949
1950 -- Set `Player` to player of the backpack the tool is in
1951 if Parent and Parent:IsA 'Backpack' then
1952 Player = Parent.Parent;
1953
1954 -- Set `Player` to player of the character holding the tool
1955 elseif Parent and Parent:IsA 'Model' then
1956 Player = Players:GetPlayerFromCharacter(Parent);
1957
1958 -- Clear `Player` if tool is not parented to a player
1959 else
1960 Player = nil;
1961 end;
1962
1963 end);
1964
1965end;
1966
1967-- Provide an interface into the module
1968return {
1969
1970 -- Provide access to internal options
1971 Options = Options;
1972
1973 -- Provide client actions API
1974 PerformAction = function (Client, ActionName, ...)
1975
1976 -- Make sure the action exists
1977 local Action = Actions[ActionName];
1978 if not Action then
1979 return;
1980 end;
1981
1982 -- Ensure client is current player in tool mode
1983 if ToolMode == 'Tool' then
1984 assert(Player and (Client == Player), 'Permission denied for client');
1985 end;
1986
1987 -- Execute valid actions
1988 return Action(...);
1989
1990 end;
1991
1992};
1993Assets = {
1994 DarkSlantedRectangle = 'rbxassetid://127774197';
1995 LightSlantedRectangle = 'rbxassetid://127772502';
1996 ActionCompletionSound = 'rbxassetid://99666917';
1997 ExpandArrow = 'rbxassetid://134367382';
1998 UndoActiveDecal = 'rbxassetid://141741408';
1999 UndoInactiveDecal = 'rbxassetid://142074557';
2000 RedoActiveDecal = 'rbxassetid://141741327';
2001 RedoInactiveDecal = 'rbxassetid://142074553';
2002 DeleteActiveDecal = 'rbxassetid://141896298';
2003 DeleteInactiveDecal = 'rbxassetid://142074644';
2004 ExportActiveDecal = 'rbxassetid://141741337';
2005 ExportInactiveDecal = 'rbxassetid://142074569';
2006 CloneActiveDecal = 'rbxassetid://142073926';
2007 CloneInactiveDecal = 'rbxassetid://142074563';
2008 PluginIcon = 'rbxassetid://142287521';
2009 GroupLockIcon = 'rbxassetid://175396862';
2010 GroupUnlockIcon = 'rbxassetid://160408836';
2011 GroupUpdateOKIcon = 'rbxassetid://164421681';
2012 GroupUpdateIcon = 'rbxassetid://160402908';
2013 CheckedCheckbox = 'rbxassetid://401518893';
2014 UncheckedCheckbox = 'rbxassetid://401518903';
2015 SemicheckedCheckbox = 'rbxassetid://404298168';
2016 AnchorIcon = 'rbxassetid://141741323';
2017 CollisionIcon = 'rbxassetid://141809596';
2018 DecorateIcon = 'rbxassetid://141741412';
2019 LightingIcon = 'rbxassetid://141741341';
2020 MaterialIcon = 'rbxassetid://141809090';
2021 MeshIcon = 'rbxassetid://141806786';
2022 MoveIcon = 'rbxassetid://141741366';
2023 NewPartIcon = 'rbxassetid://141741393';
2024 PaintIcon = 'rbxassetid://141741444';
2025 ResizeIcon = 'rbxassetid://141794324';
2026 RotateIcon = 'rbxassetid://141807775';
2027 SurfaceIcon = 'rbxassetid://141803491';
2028 TextureIcon = 'rbxassetid://141805275';
2029 WeldIcon = 'rbxassetid://141741418';
2030};
2031
2032return Assets;
2033-- Libraries
2034local Core = require(script.Parent.Core);
2035local Support = Core.Support;
2036
2037-- Initialize module
2038local BoundingBoxModule = {};
2039
2040-- Initialize internal module state
2041local StaticParts = {};
2042local StaticPartsIndex = {};
2043local StaticPartMonitors = {};
2044local RecalculateStaticExtents = true;
2045local AggregatingStaticParts = false;
2046local StaticPartAggregators = {};
2047local PotentialPartMonitors = {};
2048
2049function BoundingBoxModule.StartBoundingBox(HandleAttachmentCallback)
2050 -- Creates and starts a selection bounding box
2051
2052 -- Make sure there isn't already a bounding box
2053 if BoundingBoxEnabled then
2054 return;
2055 end;
2056
2057 -- Indicate that the bounding box is enabled
2058 BoundingBoxEnabled = true;
2059
2060 -- Create the box
2061 BoundingBox = Core.Create 'Part' {
2062 Name = 'BTBoundingBox';
2063 CanCollide = false;
2064 Transparency = 1;
2065 Anchored = true;
2066 Locked = true;
2067 };
2068
2069 -- Make the mouse ignore it
2070 Core.Mouse.TargetFilter = BoundingBox;
2071
2072 -- Make sure to calculate our static extents
2073 RecalculateStaticExtents = true;
2074 StartAggregatingStaticParts();
2075
2076 -- Store handle attachment callback
2077 BoundingBoxHandleCallback = HandleAttachmentCallback;
2078
2079 -- Begin the bounding box's updater
2080 BoundingBoxModule.UpdateBoundingBox();
2081 BoundingBoxUpdater = Support.ScheduleRecurringTask(BoundingBoxModule.UpdateBoundingBox, 0.05);
2082
2083 -- Attach handles if requested
2084 if BoundingBoxHandleCallback then
2085 BoundingBoxHandleCallback(BoundingBox);
2086 end;
2087
2088end;
2089
2090function BoundingBoxModule.GetBoundingBox()
2091 -- Returns the current bounding box
2092
2093 -- Get and return bounding box
2094 return BoundingBox;
2095
2096end;
2097
2098function IsPhysicsStatic()
2099 -- Returns whether the game's physics are active or static
2100
2101 -- Determine value if not yet cached
2102 if _IsPhysicsStatic == nil then
2103 _IsPhysicsStatic = (Core.Mode == 'Plugin') and (Workspace.DistributedGameTime == 0);
2104 end;
2105
2106 -- Return cached value
2107 return _IsPhysicsStatic;
2108
2109end;
2110
2111function BoundingBoxModule.UpdateBoundingBox()
2112 -- Updates the bounding box to fit the selection's extents
2113
2114 -- Make sure the bounding box is enabled
2115 if not BoundingBoxEnabled then
2116 return;
2117 end;
2118
2119 -- If the bounding box is inactive, and should now be active, update it
2120 if InactiveBoundingBox and #Core.Selection.Items > 0 then
2121 BoundingBox = InactiveBoundingBox;
2122 InactiveBoundingBox = nil;
2123 BoundingBoxHandleCallback(BoundingBox);
2124
2125 -- If the bounding box is active, and there are no parts, disable it
2126 elseif BoundingBox and #Core.Selection.Items == 0 then
2127 InactiveBoundingBox = BoundingBox;
2128 BoundingBox = nil;
2129 BoundingBoxHandleCallback(BoundingBox);
2130 return;
2131
2132 -- Don't try to update the bounding box if there are no parts
2133 elseif #Core.Selection.Items == 0 then
2134 return;
2135 end;
2136
2137 -- Recalculate the extents of static items as needed only
2138 if RecalculateStaticExtents then
2139 BoundingBoxModule.StaticExtents = BoundingBoxModule.CalculateExtents(StaticParts, nil, true);
2140 RecalculateStaticExtents = false;
2141 end;
2142
2143 -- Update the bounding box
2144 local BoundingBoxSize, BoundingBoxCFrame = BoundingBoxModule.CalculateExtents(Core.Selection.Items, BoundingBoxModule.StaticExtents);
2145 BoundingBox.Size = BoundingBoxSize;
2146 BoundingBox.CFrame = BoundingBoxCFrame;
2147
2148end;
2149
2150function BoundingBoxModule.ClearBoundingBox()
2151 -- Clears the selection bounding box
2152
2153 -- Make sure there's a bounding box
2154 if not BoundingBoxEnabled then
2155 return;
2156 end;
2157
2158 -- If there's a bounding box updater, stop it
2159 if BoundingBoxUpdater then
2160 BoundingBoxUpdater:Stop();
2161 BoundingBoxUpdater = nil;
2162 end;
2163
2164 -- Stop tracking static parts
2165 StopAggregatingStaticParts();
2166
2167 -- Delete the bounding box
2168 if BoundingBox then
2169 BoundingBox:Destroy();
2170 BoundingBox = nil;
2171 elseif InactiveBoundingBox then
2172 InactiveBoundingBox:Destroy();
2173 InactiveBoundingBox = nil;
2174 end;
2175
2176 -- Mark the bounding box as disabled
2177 BoundingBoxEnabled = false;
2178
2179 -- Clear the bounding box handle callback
2180 BoundingBoxHandleCallback(nil);
2181 BoundingBoxHandleCallback = nil;
2182
2183end;
2184
2185function AddStaticParts(Parts)
2186 -- Adds the static parts to the list for state tracking
2187
2188 -- Add each given part
2189 for _, Part in pairs(Parts) do
2190
2191 -- Ensure part isn't already indexed, and verify it is static
2192 if not StaticPartsIndex[Part] and (IsPhysicsStatic() or Part.Anchored) then
2193
2194 -- Add part to static index
2195 StaticPartsIndex[Part] = true;
2196
2197 -- Monitor static part for changes
2198 AddStaticPartMonitor(Part);
2199
2200 end;
2201
2202 end;
2203
2204 -- Update the static parts list
2205 StaticParts = Support.Keys(StaticPartsIndex);
2206
2207 -- Recalculate static extents to include added parts
2208 RecalculateStaticExtents = true;
2209
2210end;
2211
2212function AddStaticPartMonitor(Part)
2213 -- Monitors the given part to track when it is no longer static
2214
2215 -- Ensure part is static and isn't already monitored
2216 if not StaticPartsIndex[Part] or StaticPartMonitors[Part] then
2217 return;
2218 end;
2219
2220 -- Start monitoring part for changes
2221 StaticPartMonitors[Part] = Part.Changed:connect(function (Property)
2222
2223 -- Trigger static extent recalculations on position or size changes
2224 if Property == 'CFrame' or Property == 'Size' then
2225 RecalculateStaticExtents = true;
2226
2227 -- Remove part from static index if it becomes mobile
2228 elseif Property == 'Anchored' and not IsPhysicsStatic() and not Part.Anchored then
2229 RemoveStaticParts { Part };
2230 end;
2231
2232 end);
2233
2234end;
2235
2236function RemoveStaticParts(Parts)
2237 -- Removes the given parts from the static parts index
2238
2239 -- Remove each given part
2240 for _, Part in pairs(Parts) do
2241
2242 -- Remove part from static parts index
2243 StaticPartsIndex[Part] = nil;
2244
2245 -- Clean up the part's change monitors
2246 if StaticPartMonitors[Part] then
2247 StaticPartMonitors[Part]:disconnect();
2248 StaticPartMonitors[Part] = nil;
2249 end;
2250
2251 end;
2252
2253 -- Update the static parts list
2254 StaticParts = Support.Keys(StaticPartsIndex);
2255
2256 -- Recalculate static extents to exclude removed parts
2257 RecalculateStaticExtents = true;
2258
2259end;
2260
2261function StartAggregatingStaticParts()
2262 -- Begins to look for and identify static parts
2263
2264 -- Add current qualifying parts to static parts index
2265 AddStaticParts(Core.Selection.Items);
2266
2267 -- Watch for parts that become static
2268 for Part in pairs(Core.Selection.ItemIndex) do
2269 AddPotentialPartMonitor(Part);
2270 end;
2271
2272 -- Watch newly selected parts
2273 table.insert(StaticPartAggregators, Core.Selection.ItemsAdded:connect(function (Parts)
2274
2275 -- Add qualifying parts to static parts index
2276 AddStaticParts(Parts);
2277
2278 -- Watch for parts that become anchored
2279 for _, Part in pairs(Parts) do
2280 AddPotentialPartMonitor(Part);
2281 end;
2282
2283 end));
2284
2285 -- Remove deselected parts from static parts index
2286 table.insert(StaticPartAggregators, Core.Selection.ItemsRemoved:connect(function (Parts)
2287 RemoveStaticParts(Parts);
2288 for _, Part in pairs(Parts) do
2289 if PotentialPartMonitors[Part] then
2290 PotentialPartMonitors[Part]:disconnect();
2291 PotentialPartMonitors[Part] = nil;
2292 end;
2293 end;
2294 end));
2295
2296end;
2297
2298function BoundingBoxModule.RecalculateStaticExtents()
2299 -- Sets flag indicating that extents of static items should be recalculated
2300
2301 -- Set flag to trigger recalculation on the next step in the update loop
2302 RecalculateStaticExtents = true;
2303
2304end;
2305
2306function AddPotentialPartMonitor(Part)
2307 -- Monitors the given part to track when it becomes static
2308
2309 -- Ensure part is not already monitored
2310 if PotentialPartMonitors[Part] then
2311 return;
2312 end;
2313
2314 -- Create anchored state change monitor
2315 PotentialPartMonitors[Part] = Part.Changed:connect(function (Property)
2316 if Property == 'Anchored' and Part.Anchored then
2317 AddStaticParts { Part };
2318 end;
2319 end);
2320
2321end;
2322
2323function BoundingBoxModule.PauseMonitoring()
2324 -- Disables part monitors
2325
2326 -- Disconnect all potential part monitors
2327 for Part, Monitor in pairs(PotentialPartMonitors) do
2328 Monitor:disconnect();
2329 PotentialPartMonitors[Part] = nil;
2330 end;
2331
2332 -- Disconnect all static part monitors
2333 for Part, Monitor in pairs(StaticPartMonitors) do
2334 Monitor:disconnect();
2335 StaticPartMonitors[Part] = nil;
2336 end;
2337
2338 -- Stop update loop
2339 if BoundingBoxUpdater then
2340 BoundingBoxUpdater:Stop();
2341 BoundingBoxUpdater = nil;
2342 end;
2343
2344end;
2345
2346function BoundingBoxModule.ResumeMonitoring()
2347 -- Starts update loop and part monitors for selected and indexed parts
2348
2349 -- Ensure bounding box is enabled
2350 if not BoundingBoxEnabled then
2351 return;
2352 end;
2353
2354 -- Start static part monitors
2355 for StaticPart in pairs(StaticPartsIndex) do
2356 AddStaticPartMonitor(StaticPart);
2357 end;
2358
2359 -- Start potential part monitors
2360 for Part in pairs(Core.Selection.ItemIndex) do
2361 AddPotentialPartMonitor(Part);
2362 end;
2363
2364 -- Start update loop
2365 if not BoundingBoxUpdater then
2366 BoundingBoxUpdater = Support.ScheduleRecurringTask(BoundingBoxModule.UpdateBoundingBox, 0.05);
2367 end;
2368
2369end;
2370
2371function StopAggregatingStaticParts()
2372 -- Stops looking for static parts, clears unnecessary data
2373
2374 -- Disconnect all aggregators
2375 for AggregatorKey, Aggregator in pairs(StaticPartAggregators) do
2376 Aggregator:disconnect();
2377 StaticPartAggregators[AggregatorKey] = nil;
2378 end;
2379
2380 -- Remove all static part monitors
2381 for MonitorKey, Monitor in pairs(StaticPartMonitors) do
2382 Monitor:disconnect();
2383 StaticPartMonitors[MonitorKey] = nil;
2384 end;
2385
2386 -- Remove all potential part monitors
2387 for MonitorKey, Monitor in pairs(PotentialPartMonitors) do
2388 Monitor:disconnect();
2389 PotentialPartMonitors[MonitorKey] = nil;
2390 end;
2391
2392 -- Clear all static part information
2393 StaticParts = {};
2394 StaticPartsIndex = {};
2395 BoundingBoxModule.StaticExtents = nil;
2396
2397end;
2398
2399-- Create shortcuts to avoid intensive lookups
2400local CFrame_new = CFrame.new;
2401local table_insert = table.insert;
2402local CFrame_toWorldSpace = CFrame.new().toWorldSpace;
2403local math_min = math.min;
2404local math_max = math.max;
2405local unpack = unpack;
2406
2407function BoundingBoxModule.CalculateExtents(Items, StaticExtents, ExtentsOnly)
2408 -- Returns the size and position of a box covering all items in `Items`
2409
2410 -- Ensure there are items
2411 if #Items == 0 then
2412 return;
2413 end;
2414
2415 -- Get initial extents data for comparison
2416 local ComparisonBaseMin = StaticExtents and StaticExtents.Min or Items[1].Position;
2417 local ComparisonBaseMax = StaticExtents and StaticExtents.Max or Items[1].Position;
2418 local MinX, MinY, MinZ = ComparisonBaseMin.X, ComparisonBaseMin.Y, ComparisonBaseMin.Z;
2419 local MaxX, MaxY, MaxZ = ComparisonBaseMax.X, ComparisonBaseMax.Y, ComparisonBaseMax.Z;
2420
2421 -- Go through each part in `Items`
2422 for _, Part in pairs(Items) do
2423
2424 -- Avoid re-calculating for static parts
2425 if not ((IsPhysicsStatic() or Part.Anchored) and StaticExtents) then
2426
2427 -- Get shortcuts to part data
2428 local PartCFrame = Part.CFrame;
2429 local PartSize = Part.Size / 2;
2430 local SizeX, SizeY, SizeZ = PartSize.X, PartSize.Y, PartSize.Z;
2431
2432 local Corner;
2433 local XPoints, YPoints, ZPoints = {}, {}, {};
2434
2435 Corner = PartCFrame * CFrame_new(SizeX, SizeY, SizeZ);
2436 table_insert(XPoints, Corner.x);
2437 table_insert(YPoints, Corner.y);
2438 table_insert(ZPoints, Corner.z);
2439
2440 Corner = PartCFrame * CFrame_new(-SizeX, SizeY, SizeZ);
2441 table_insert(XPoints, Corner.x);
2442 table_insert(YPoints, Corner.y);
2443 table_insert(ZPoints, Corner.z);
2444
2445 Corner = PartCFrame * CFrame_new(SizeX, -SizeY, SizeZ);
2446 table_insert(XPoints, Corner.x);
2447 table_insert(YPoints, Corner.y);
2448 table_insert(ZPoints, Corner.z);
2449
2450 Corner = PartCFrame * CFrame_new(SizeX, SizeY, -SizeZ);
2451 table_insert(XPoints, Corner.x);
2452 table_insert(YPoints, Corner.y);
2453 table_insert(ZPoints, Corner.z);
2454
2455 Corner = PartCFrame * CFrame_new(-SizeX, SizeY, -SizeZ);
2456 table_insert(XPoints, Corner.x);
2457 table_insert(YPoints, Corner.y);
2458 table_insert(ZPoints, Corner.z);
2459
2460 Corner = PartCFrame * CFrame_new(-SizeX, -SizeY, SizeZ);
2461 table_insert(XPoints, Corner.x);
2462 table_insert(YPoints, Corner.y);
2463 table_insert(ZPoints, Corner.z);
2464
2465 Corner = PartCFrame * CFrame_new(SizeX, -SizeY, -SizeZ);
2466 table_insert(XPoints, Corner.x);
2467 table_insert(YPoints, Corner.y);
2468 table_insert(ZPoints, Corner.z);
2469
2470 Corner = PartCFrame * CFrame_new(-SizeX, -SizeY, -SizeZ);
2471 table_insert(XPoints, Corner.x);
2472 table_insert(YPoints, Corner.y);
2473 table_insert(ZPoints, Corner.z);
2474
2475 -- Reduce gathered points to min/max extents
2476 MinX = math_min(MinX, unpack(XPoints));
2477 MinY = math_min(MinY, unpack(YPoints));
2478 MinZ = math_min(MinZ, unpack(ZPoints));
2479 MaxX = math_max(MaxX, unpack(XPoints));
2480 MaxY = math_max(MaxY, unpack(YPoints));
2481 MaxZ = math_max(MaxZ, unpack(ZPoints));
2482
2483 end;
2484
2485 end;
2486
2487 -- Calculate the extents
2488 local Extents = {
2489 Min = Vector3.new(MinX, MinY, MinZ),
2490 Max = Vector3.new(MaxX, MaxY, MaxZ);
2491 };
2492
2493 -- Only return extents if requested
2494 if ExtentsOnly then
2495 return Extents;
2496 end;
2497
2498 -- Calculate the bounding box size
2499 local Size = Vector3.new(
2500 MaxX - MinX,
2501 MaxY - MinY,
2502 MaxZ - MinZ
2503 );
2504
2505 -- Calculate the bounding box center
2506 local Position = CFrame.new(
2507 MinX + (MaxX - MinX) / 2,
2508 MinY + (MaxY - MinY) / 2,
2509 MinZ + (MaxZ - MinZ) / 2
2510 );
2511
2512 -- Return the size and position
2513 return Size, Position;
2514
2515end;
2516
2517return BoundingBoxModule;
2518Tool = script.Parent;
2519
2520-- Await initialization
2521repeat wait() until _G[Tool];
2522Plugin = _G[Tool].Plugin;
2523
2524-- Detect mode
2525Mode = Plugin and 'Plugin' or 'Tool';
2526
2527-- Load tool completely
2528local Indicator = Tool:WaitForChild 'Loaded';
2529while not Indicator.Value do
2530 Indicator.Changed:wait();
2531end;
2532
2533-- Libraries
2534Security = require(Tool.SecurityModule);
2535History = require(Tool.HistoryModule);
2536Selection = require(Tool.SelectionModule);
2537Targeting = require(Tool.TargetingModule);
2538Region = require(Tool['Region by AxisAngle']);
2539RbxUtility = LoadLibrary 'RbxUtility';
2540Create = RbxUtility.Create;
2541
2542-- Load additional libraries
2543while not _G.GetLibraries do wait() end
2544Support, Cheer, Try = _G.GetLibraries(
2545 'F3X/SupportLibrary@^1.0.0',
2546 'F3X/Cheer@^0.0.0',
2547 'F3X/Try@~1.0.0'
2548);
2549
2550-- References
2551Support.ImportServices();
2552SyncAPI = Tool.SyncAPI;
2553Player = Players.LocalPlayer;
2554
2555-- Preload assets
2556Assets = require(Tool.Assets);
2557ContentProvider:PreloadAsync(Support.Values(Assets));
2558
2559-- Core events
2560ToolChanged = RbxUtility.CreateSignal();
2561
2562function EquipTool(Tool)
2563 -- Equips and switches to the given tool
2564
2565 -- Unequip current tool
2566 if CurrentTool and CurrentTool.Equipped then
2567 CurrentTool.Unequip();
2568 CurrentTool.Equipped = false;
2569 end;
2570
2571 -- Set `Tool` as current
2572 CurrentTool = Tool;
2573 CurrentTool.Equipped = true;
2574
2575 -- Fire relevant events
2576 ToolChanged:fire(Tool);
2577
2578 -- Equip the tool
2579 Tool.Equip();
2580
2581end;
2582
2583function RecolorHandle(Color)
2584 SyncAPI:Invoke('RecolorHandle', Color);
2585end;
2586
2587-- Theme UI to current tool
2588ToolChanged:connect(function (Tool)
2589 coroutine.wrap(RecolorHandle)(Tool.Color);
2590 coroutine.wrap(Selection.RecolorOutlines)(Tool.Color);
2591end);
2592
2593-- Core hotkeys
2594Hotkeys = {};
2595
2596function AssignHotkey(Hotkey, Callback)
2597 -- Assigns the given hotkey to `Callback`
2598
2599 -- Standardize enum-described hotkeys
2600 if type(Hotkey) == 'userdata' then
2601 Hotkey = { Hotkey };
2602
2603 -- Standardize string-described hotkeys
2604 elseif type(Hotkey) == 'string' then
2605 Hotkey = { Enum.KeyCode[Hotkey] };
2606
2607 -- Standardize string table-described hotkeys
2608 elseif type(Hotkey) == 'table' then
2609 for Index, Key in ipairs(Hotkey) do
2610 if type(Key) == 'string' then
2611 Hotkey[Index] = Enum.KeyCode[Key];
2612 end;
2613 end;
2614 end;
2615
2616 -- Register the hotkey
2617 table.insert(Hotkeys, { Keys = Hotkey, Callback = Callback });
2618
2619end;
2620
2621function EnableHotkeys()
2622 -- Begins to listen for hotkey triggering
2623
2624 -- Listen for pressed keys
2625 Connections.Hotkeys = Support.AddUserInputListener('Began', 'Keyboard', false, function (Input)
2626 local _PressedKeys = Support.GetListMembers(UserInputService:GetKeysPressed(), 'KeyCode');
2627
2628 -- Filter out problematic keys
2629 local PressedKeys = {};
2630 local FilteredKeys = Support.FlipTable { 'LeftAlt', 'W', 'S', 'A', 'D', 'Space' };
2631 for _, Key in ipairs(_PressedKeys) do
2632 if not FilteredKeys[Key.Name] then
2633 table.insert(PressedKeys, Key);
2634 end;
2635 end;
2636
2637 -- Count pressed keys
2638 local KeyCount = #PressedKeys;
2639
2640 -- Prioritize hotkeys based on # of required keys
2641 table.sort(Hotkeys, function (A, B)
2642 if #A.Keys > #B.Keys then
2643 return true;
2644 end;
2645 end);
2646
2647 -- Identify matching hotkeys
2648 for _, Hotkey in ipairs(Hotkeys) do
2649 if KeyCount == #Hotkey.Keys then
2650
2651 -- Get the hotkey's key index
2652 local Keys = Support.FlipTable(Hotkey.Keys)
2653 local MatchingKeys = 0;
2654
2655 -- Check matching pressed keys
2656 for _, PressedKey in pairs(PressedKeys) do
2657 if Keys[PressedKey] then
2658 MatchingKeys = MatchingKeys + 1;
2659 end;
2660 end;
2661
2662 -- Trigger the first matching hotkey's callback
2663 if MatchingKeys == KeyCount then
2664 Hotkey.Callback();
2665 break;
2666 end;
2667
2668 end;
2669 end;
2670 end);
2671
2672end;
2673
2674Enabling = RbxUtility.CreateSignal();
2675Disabling = RbxUtility.CreateSignal();
2676Enabled = RbxUtility.CreateSignal();
2677Disabled = RbxUtility.CreateSignal();
2678
2679function Enable(Mouse)
2680
2681 -- Ensure tool is disabled or disabling, and not already enabling
2682 if (IsEnabled and not IsDisabling) or IsEnabling then
2683 return;
2684
2685 -- If tool is disabling, enable it once fully disabled
2686 elseif IsDisabling then
2687 Disabled:Wait();
2688 return Enable(Mouse);
2689 end;
2690
2691 -- Indicate that tool is enabling
2692 IsEnabling = true;
2693 Enabling:Fire();
2694
2695 -- Update the core mouse
2696 getfenv(0).Mouse = Mouse;
2697
2698 -- Use default mouse behavior
2699 UserInputService.MouseBehavior = Enum.MouseBehavior.Default;
2700
2701 -- Disable mouse lock in tool mode
2702 if Mode == 'Tool' then
2703 SyncAPI:Invoke('SetMouseLockEnabled', false);
2704 end;
2705
2706 -- Wait for UI to initialize asynchronously
2707 while not UI do
2708 wait(0.1);
2709 end;
2710
2711 -- Show UI
2712 UI.Parent = UIContainer;
2713
2714 -- Display startup notifications
2715 if not StartupNotificationsDisplayed then
2716 Cheer(Tool.Interfaces.Notifications, UI).Start(getfenv(0));
2717 StartupNotificationsDisplayed = true;
2718 end;
2719
2720 -- Start systems
2721 EnableHotkeys();
2722 Targeting.EnableTargeting();
2723 Selection.EnableOutlines();
2724 Selection.EnableMultiselectionHotkeys();
2725
2726 -- Equip current tool
2727 EquipTool(CurrentTool or require(Tool.Tools.MoveTool));
2728
2729 -- Indicate that tool is now enabled
2730 IsEnabled = true;
2731 IsEnabling = false;
2732 Enabled:Fire();
2733
2734end;
2735
2736function Disable()
2737
2738 -- Ensure tool is enabled or enabling, and not already disabling
2739 if (not IsEnabled and not IsEnabling) or IsDisabling then
2740 return;
2741
2742 -- If tool is enabling, disable it once fully enabled
2743 elseif IsEnabling then
2744 Enabled:Wait();
2745 return Disable();
2746 end;
2747
2748 -- Indicate that tool is now disabling
2749 IsDisabling = true;
2750 Disabling:Fire();
2751
2752 -- Reenable mouse lock option in tool mode
2753 if Mode == 'Tool' then
2754 pcall(SyncAPI.Invoke, SyncAPI, 'SetMouseLockEnabled', true);
2755 end;
2756
2757 -- Hide UI
2758 if UI then
2759 UI.Parent = script;
2760 end;
2761
2762 -- Unequip current tool
2763 if CurrentTool then
2764 CurrentTool.Unequip();
2765 CurrentTool.Equipped = false;
2766 end;
2767
2768 -- Clear temporary connections
2769 ClearConnections();
2770
2771 -- Indicate that tool is now disabled
2772 IsEnabled = false;
2773 IsDisabling = false;
2774 Disabled:Fire();
2775
2776end;
2777
2778
2779-- Core connections
2780Connections = {};
2781
2782function ClearConnections()
2783 -- Clears and disconnects temporary connections
2784 for Index, Connection in pairs(Connections) do
2785 Connection:disconnect();
2786 Connections[Index] = nil;
2787 end;
2788end;
2789
2790function InitializeUI()
2791 -- Sets up the UI
2792
2793 -- Ensure UI has not yet been initialized
2794 if UI then
2795 return;
2796 end;
2797
2798 -- Create the root UI
2799 UI = Create 'ScreenGui' {
2800 Name = 'Building Tools by F3X (UI)'
2801 };
2802
2803 -- Set up dock
2804 Dock = Cheer(Tool.Interfaces.Dock, UI).Start(getfenv(0));
2805
2806end;
2807
2808-- Enable tool or plugin
2809if Mode == 'Plugin' then
2810
2811 -- Set the UI root
2812 UIContainer = CoreGui;
2813
2814 -- Create the toolbar button
2815 PluginButton = Plugin:CreateToolbar('Building Tools by F3X'):CreateButton(
2816 'Building Tools by F3X',
2817 'Building Tools by F3X',
2818 Assets.PluginIcon
2819 );
2820
2821 -- Connect the button to the system
2822 PluginButton.Click:connect(function ()
2823 PluginEnabled = not PluginEnabled;
2824 PluginButton:SetActive(PluginEnabled);
2825
2826 -- Toggle the tool
2827 if PluginEnabled then
2828 Plugin:Activate(true);
2829 Enable(Plugin:GetMouse());
2830 else
2831 Disable();
2832 end;
2833 end);
2834
2835 -- Disable the tool upon plugin deactivation
2836 Plugin.Deactivation:connect(Disable);
2837
2838 -- Sync Studio selection to internal selection
2839 Selection.Changed:connect(function ()
2840 SelectionService:Set(Selection.Items);
2841 end);
2842
2843 -- Sync internal selection to Studio selection on enabling
2844 Enabling:connect(function ()
2845 Selection.Replace(SelectionService:Get());
2846 end);
2847
2848 -- Roughly sync Studio history to internal history (API lacking necessary functionality)
2849 History.Changed:connect(function ()
2850 ChangeHistoryService:SetWaypoint 'Building Tools by F3X';
2851 end);
2852
2853elseif Mode == 'Tool' then
2854
2855 -- Set the UI root
2856 UIContainer = Player:WaitForChild 'PlayerGui';
2857
2858 -- Connect the tool to the system
2859 Tool.Equipped:connect(Enable);
2860 Tool.Unequipped:connect(Disable);
2861
2862 -- Disable the tool if not parented
2863 if not Tool.Parent then
2864 Disable();
2865 end;
2866
2867 -- Disable the tool automatically if not equipped or in backpack
2868 Tool.AncestryChanged:connect(function (Item, Parent)
2869 if not Parent or not (Parent:IsA 'Backpack' or (Parent:IsA 'Model' and Players:GetPlayerFromCharacter(Parent))) then
2870 Disable();
2871 end;
2872 end);
2873
2874end;
2875
2876-- Assign hotkeys for undoing (left or right shift + Z)
2877AssignHotkey({ 'LeftShift', 'Z' }, History.Undo);
2878AssignHotkey({ 'RightShift', 'Z' }, History.Undo);
2879
2880-- Assign hotkeys for redoing (left or right shift + Y)
2881AssignHotkey({ 'LeftShift', 'Y' }, History.Redo);
2882AssignHotkey({ 'RightShift', 'Y' }, History.Redo);
2883
2884-- If in-game, enable ctrl hotkeys for undoing and redoing
2885if Mode == 'Tool' then
2886 AssignHotkey({ 'LeftControl', 'Z' }, History.Undo);
2887 AssignHotkey({ 'RightControl', 'Z' }, History.Undo);
2888 AssignHotkey({ 'LeftControl', 'Y' }, History.Redo);
2889 AssignHotkey({ 'RightControl', 'Y' }, History.Redo);
2890end;
2891
2892function CloneSelection()
2893 -- Clones selected parts
2894
2895 -- Make sure that there are items in the selection
2896 if #Selection.Items == 0 then
2897 return;
2898 end;
2899
2900 -- Send the cloning request to the server
2901 local Clones = SyncAPI:Invoke('Clone', Selection.Items);
2902
2903 -- Put together the history record
2904 local HistoryRecord = {
2905 Clones = Clones;
2906
2907 Unapply = function (HistoryRecord)
2908 -- Reverts this change
2909
2910 -- Deselect the clones
2911 Selection.Remove(HistoryRecord.Clones, false);
2912
2913 -- Remove the clones
2914 SyncAPI:Invoke('Remove', HistoryRecord.Clones);
2915
2916 end;
2917
2918 Apply = function (HistoryRecord)
2919 -- Reapplies this change
2920
2921 -- Restore the clones
2922 SyncAPI:Invoke('UndoRemove', HistoryRecord.Clones);
2923
2924 end;
2925
2926 };
2927
2928 -- Register the history record
2929 History.Add(HistoryRecord);
2930
2931 -- Select the clones
2932 Selection.Replace(Clones);
2933
2934 -- Play a confirmation sound
2935 PlayConfirmationSound();
2936
2937 -- Flash the outlines of the new parts
2938 coroutine.wrap(Selection.FlashOutlines)();
2939
2940end;
2941
2942ConfirmationSound = Create 'Sound' {
2943 Name = 'BTActionCompletionSound';
2944 Pitch = 1.5;
2945 SoundId = Assets.ActionCompletionSound;
2946 Volume = 1;
2947};
2948
2949function PlayConfirmationSound()
2950 -- Plays a confirmation beep sound
2951
2952 -- Trigger the sound locally
2953 SoundService:PlayLocalSound(ConfirmationSound);
2954
2955end;
2956
2957function DeleteSelection()
2958 -- Deletes selected items
2959
2960 -- Put together the history record
2961 local HistoryRecord = {
2962 Parts = Support.CloneTable(Selection.Items);
2963
2964 Unapply = function (HistoryRecord)
2965 -- Reverts this change
2966
2967 -- Restore the parts
2968 SyncAPI:Invoke('UndoRemove', HistoryRecord.Parts);
2969
2970 -- Select the restored parts
2971 Selection.Replace(HistoryRecord.Parts);
2972
2973 end;
2974
2975 Apply = function (HistoryRecord)
2976 -- Applies this change
2977
2978 -- Deselect the parts
2979 Selection.Remove(HistoryRecord.Parts, false);
2980
2981 -- Remove the parts
2982 SyncAPI:Invoke('Remove', HistoryRecord.Parts);
2983
2984 end;
2985
2986 };
2987
2988 -- Deselect parts before deleting
2989 Selection.Remove(HistoryRecord.Parts, false);
2990
2991 -- Perform the removal
2992 SyncAPI:Invoke('Remove', HistoryRecord.Parts);
2993
2994 -- Register the history record
2995 History.Add(HistoryRecord);
2996
2997end;
2998
2999-- Assign hotkeys for cloning (left or right shift + c)
3000AssignHotkey({ 'LeftShift', 'C' }, CloneSelection);
3001AssignHotkey({ 'RightShift', 'C' }, CloneSelection);
3002
3003-- Assign hotkeys for deletion (left or right shift + X)
3004AssignHotkey({ 'LeftShift', 'X' }, DeleteSelection);
3005AssignHotkey({ 'RightShift', 'X' }, DeleteSelection);
3006
3007-- If in-game, enable ctrl hotkeys for cloning and deleting
3008if Mode == 'Tool' then
3009 AssignHotkey({ 'LeftControl', 'C' }, CloneSelection);
3010 AssignHotkey({ 'RightControl', 'C' }, CloneSelection);
3011 AssignHotkey({ 'LeftControl', 'X' }, DeleteSelection);
3012 AssignHotkey({ 'RightControl', 'X' }, DeleteSelection);
3013end;
3014
3015-- Assign hotkeys for prism selection
3016AssignHotkey({ 'LeftShift', 'K' }, Targeting.PrismSelect);
3017AssignHotkey({ 'RightShift', 'K' }, Targeting.PrismSelect);
3018
3019-- If in-game, enable ctrl hotkeys for prism selection
3020if Mode == 'Tool' then
3021 AssignHotkey({ 'LeftControl', 'K' }, Targeting.PrismSelect);
3022 AssignHotkey({ 'RightControl', 'K' }, Targeting.PrismSelect);
3023end;
3024
3025-- Assign hotkeys for sibling selection
3026AssignHotkey({ 'LeftBracket' }, Support.Call(Targeting.SelectSiblings, false, true));
3027AssignHotkey({ 'LeftShift', 'LeftBracket' }, Support.Call(Targeting.SelectSiblings, false, false));
3028AssignHotkey({ 'RightShift', 'LeftBracket' }, Support.Call(Targeting.SelectSiblings, false, false));
3029
3030-- Assign hotkeys for selection clearing
3031AssignHotkey({ 'LeftShift', 'R' }, Support.Call(Selection.Clear, true));
3032AssignHotkey({ 'RightShift', 'R' }, Support.Call(Selection.Clear, true));
3033
3034-- If in-game, enable ctrl hotkeys for sibling selection & selection clearing
3035if Mode == 'Tool' then
3036 AssignHotkey({ 'LeftControl', 'LeftBracket' }, Support.Call(Targeting.SelectSiblings, false, false));
3037 AssignHotkey({ 'RightControl', 'LeftBracket' }, Support.Call(Targeting.SelectSiblings, false, false));
3038 AssignHotkey({ 'LeftControl', 'R' }, Support.Call(Selection.Clear, true));
3039 AssignHotkey({ 'RightControl', 'R' }, Support.Call(Selection.Clear, true));
3040end;
3041
3042function IsSelectable(Object)
3043 -- Returns whether `Object` can be selected
3044
3045 -- Check if `Object` exists, is not locked, and is not ignored
3046 if not Object or not Object.Parent or not Object:IsA 'BasePart' or Object.Locked or IsIgnored(Object) then
3047 return false;
3048 end;
3049
3050 -- If areas are enabled, check if `Object` violates any areas
3051 if Security.AreAreasEnabled() then
3052 return not Security.ArePartsViolatingAreas({ Object }, Player, true, {});
3053 end;
3054
3055 -- If no checks fail, `Object` is selectable
3056 return Object;
3057
3058end;
3059
3060function IsIgnored(Object)
3061 -- TODO: Add ignoring capability
3062end;
3063
3064function SetParent(Parent)
3065 -- Sets the current default parent for parts
3066
3067end;
3068
3069function ExportSelection()
3070 -- Exports the selected parts
3071
3072 -- Make sure that there are items in the selection
3073 if #Selection.Items == 0 then
3074 return;
3075 end;
3076
3077 -- Start an export dialog
3078 local Dialog = Cheer(Tool.Interfaces.ExportDialog, UI).Start();
3079
3080 -- Send the exporting request to the server
3081 Try(SyncAPI.Invoke, SyncAPI, 'Export', Selection.Items)
3082
3083 -- Display creation ID on success
3084 :Then(function (CreationId)
3085 Dialog.SetResult(CreationId);
3086 PlayConfirmationSound();
3087 print('[Building Tools by F3X] Uploaded Export:', CreationId);
3088 end)
3089
3090 -- Display error messages on failure
3091 :Catch('Http requests are not enabled', function ()
3092 Dialog.SetError('Please enable HTTP requests');
3093 end)
3094 :Catch('Export failed due to server-side error', function ()
3095 Dialog.SetError('An error occurred, try again');
3096 end)
3097 :Catch('Post data too large', function ()
3098 Dialog.SetError('Try splitting up your build');
3099 end)
3100 :Catch(function (Error, Stack, Attempt)
3101 Dialog.SetError('An unknown error occurred, try again')
3102 warn('⌠[Building Tools by F3X] Failed to export selection', '\n\nError:\n', Error, '\n\nStack:\n', Stack);
3103 end);
3104
3105end;
3106
3107-- Assign hotkey for exporting selection
3108AssignHotkey({ 'LeftShift', 'P' }, ExportSelection);
3109AssignHotkey({ 'RightShift', 'P' }, ExportSelection);
3110
3111-- If in-game, enable ctrl hotkeys for exporting
3112if Mode == 'Tool' then
3113 AssignHotkey({ 'LeftControl', 'P' }, ExportSelection);
3114 AssignHotkey({ 'RightControl', 'P' }, ExportSelection);
3115end;
3116
3117function IsVersionOutdated()
3118 -- Returns whether this version of Building Tools is out of date
3119
3120 -- Check most recent version number
3121 local AssetInfo = MarketplaceService:GetProductInfo(142785488, Enum.InfoType.Asset);
3122 local LatestMajorVersion, LatestMinorVersion, LatestPatchVersion = AssetInfo.Description:match '%[Version: ([0-9]+)%.([0-9]+)%.([0-9]+)%]';
3123 local CurrentMajorVersion, CurrentMinorVersion, CurrentPatchVersion = Tool.Version.Value:match '([0-9]+)%.([0-9]+)%.([0-9]+)';
3124
3125 -- Convert version data into numbers
3126 local LatestMajorVersion, LatestMinorVersion, LatestPatchVersion =
3127 tonumber(LatestMajorVersion), tonumber(LatestMinorVersion), tonumber(LatestPatchVersion);
3128 local CurrentMajorVersion, CurrentMinorVersion, CurrentPatchVersion =
3129 tonumber(CurrentMajorVersion), tonumber(CurrentMinorVersion), tonumber(CurrentPatchVersion);
3130
3131 -- Determine whether current version is outdated
3132 if LatestMajorVersion > CurrentMajorVersion then
3133 return true;
3134 elseif LatestMajorVersion == CurrentMajorVersion then
3135 if LatestMinorVersion > CurrentMinorVersion then
3136 return true;
3137 elseif LatestMinorVersion == CurrentMinorVersion then
3138 return LatestPatchVersion > CurrentPatchVersion;
3139 end;
3140 end;
3141
3142 -- Return an up-to-date status if not oudated
3143 return false;
3144
3145end;
3146
3147function ToggleSwitch(CurrentButtonName, SwitchContainer)
3148 -- Toggles between the buttons in a switch
3149
3150 -- Reset all buttons
3151 for _, Button in pairs(SwitchContainer:GetChildren()) do
3152
3153 -- Make sure to not mistake the option label for a button
3154 if Button.Name ~= 'Label' then
3155
3156 -- Set appearance to disabled
3157 Button.SelectedIndicator.BackgroundTransparency = 1;
3158 Button.Background.Image = Assets.LightSlantedRectangle;
3159
3160 end;
3161
3162 end;
3163
3164 -- Make sure there's a new current button
3165 if CurrentButtonName then
3166
3167 -- Get the current button
3168 local CurrentButton = SwitchContainer[CurrentButtonName];
3169
3170 -- Set the current button's appearance to enabled
3171 CurrentButton.SelectedIndicator.BackgroundTransparency = 0;
3172 CurrentButton.Background.Image = Assets.DarkSlantedRectangle;
3173
3174 end;
3175end;
3176
3177-- References to reduce indexing time
3178local GetConnectedParts = Instance.new('Part').GetConnectedParts;
3179local GetChildren = script.GetChildren;
3180
3181function GetPartJoints(Part, Whitelist)
3182 -- Returns any manual joints involving `Part`
3183
3184 local Joints = {};
3185
3186 -- Get joints stored inside `Part`
3187 for Joint, JointParent in pairs(SearchJoints(Part, Part, Whitelist)) do
3188 Joints[Joint] = JointParent;
3189 end;
3190
3191 -- Get joints stored inside connected parts
3192 for _, ConnectedPart in pairs(GetConnectedParts(Part)) do
3193 for Joint, JointParent in pairs(SearchJoints(ConnectedPart, Part, Whitelist)) do
3194 Joints[Joint] = JointParent;
3195 end;
3196 end;
3197
3198 -- Return all found joints
3199 return Joints;
3200
3201end;
3202
3203-- Types of joints to assume should be preserved
3204local ManualJointTypes = Support.FlipTable { 'Weld', 'ManualWeld', 'ManualGlue', 'Motor', 'Motor6D' };
3205
3206function SearchJoints(Haystack, Part, Whitelist)
3207 -- Searches for and returns manual joints in `Haystack` involving `Part` and other parts in `Whitelist`
3208
3209 local Joints = {};
3210
3211 -- Search the haystack for joints involving `Part`
3212 for _, Item in pairs(GetChildren(Haystack)) do
3213
3214 -- Check if this item is a manual, intentional joint
3215 if ManualJointTypes[Item.ClassName] and
3216 (Whitelist[Item.Part0] and Whitelist[Item.Part1]) then
3217
3218 -- Save joint and state if intentional
3219 Joints[Item] = Item.Parent;
3220
3221 end;
3222
3223 end;
3224
3225 -- Return the found joints
3226 return Joints;
3227
3228end;
3229
3230function RestoreJoints(Joints)
3231 -- Restores the joints from the given `Joints` data
3232
3233 -- Restore each joint
3234 for Joint, JointParent in pairs(Joints) do
3235 Joint.Parent = JointParent;
3236 end;
3237
3238end;
3239
3240function PreserveJoints(Part, Whitelist)
3241 -- Preserves and returns intentional joints of `Part` connecting parts in `Whitelist`
3242
3243 -- Get the part's joints
3244 local Joints = GetPartJoints(Part, Whitelist);
3245
3246 -- Save the joints from being broken
3247 for Joint in pairs(Joints) do
3248 Joint.Parent = nil;
3249 end;
3250
3251 -- Return the joints
3252 return Joints;
3253
3254end;
3255
3256-- Initialize the UI
3257InitializeUI();
3258
3259-- Return core
3260return getfenv(0);
3261-- Libraries
3262local RbxUtility = LoadLibrary 'RbxUtility';
3263
3264History = {
3265
3266 -- Record stack
3267 Stack = {},
3268
3269 -- Current position in record stack
3270 Index = 0,
3271
3272 -- History change event
3273 Changed = RbxUtility.CreateSignal()
3274
3275};
3276
3277function History.Undo()
3278 -- Unapplies the previous record in stack
3279
3280 -- Stay within boundaries
3281 if History.Index - 1 < 0 then
3282 return;
3283 end;
3284
3285 -- Get the history record, unapply it
3286 local Record = History.Stack[History.Index];
3287 Record:Unapply();
3288
3289 -- Update the index
3290 History.Index = History.Index - 1;
3291
3292 -- Fire the Changed event
3293 History.Changed:fire();
3294
3295end;
3296
3297function History.Redo()
3298 -- Applies the next record in stack
3299
3300 -- Stay within boundaries
3301 if History.Index + 1 > #History.Stack then
3302 return;
3303 end;
3304
3305 -- Update the index
3306 History.Index = History.Index + 1;
3307
3308 -- Get the history record and apply it
3309 local Record = History.Stack[History.Index];
3310 Record:Apply();
3311
3312 -- Fire the Changed event
3313 History.Changed:fire();
3314
3315end;
3316
3317function History.Add(Record)
3318 -- Adds new history record to stack
3319
3320 -- Update the index
3321 History.Index = History.Index + 1;
3322
3323 -- Register the new history record
3324 History.Stack[History.Index] = Record;
3325
3326 -- Clear history ahead
3327 for Index = History.Index + 1, #History.Stack do
3328 History.Stack[Index] = nil;
3329 end;
3330
3331 -- Fire the Changed event
3332 History.Changed:fire();
3333
3334end;
3335
3336return History;
3337--[[
3338By AxisAngle, (Trey Reynolds)
3339Documentation
3340http://www.roblox.com/item.aspx?id=227509468
3341
3342Region constructors:
3343 Region Region.new(CFrame RegionCFrame, Vector3 RegionSize)
3344 >Returns a new Region object
3345
3346 Region Region.FromPart(Instance Part)
3347 >Returns a new Region objects
3348
3349
3350Region methods:
3351 table Region:Cast([Instance or table Ignore])
3352 >Returns all parts in the Region, ignoring the Ignore
3353
3354 bool Region:CastPart(Instance Part)
3355 >Returns true if Part is within Region, false otherwise
3356
3357 table Region:CastParts(table Parts)
3358 >Returns a table of all parts within the region
3359
3360 bool Region:CastPoint(Vector3 Point)
3361 >Returns true if Point intersects Region, false otherwise
3362
3363 bool Region:CastSphere(Vector3 SphereCenter, number SphereRadius)
3364 >Returns true if Sphere intersects Region, false otherwise
3365
3366 bool Region:CastBox(CFrame BoxCFrame, Vector3 BoxSize)
3367 >Returns true if Box intersects Region, false otherwise
3368
3369
3370
3371Region properties: (Regions are mutable)
3372 CFrame CFrame
3373 Vector3 Size
3374 Region3 Region3
3375
3376
3377
3378Region functions:
3379 Region3 Region.Region3BoundingBox(CFrame BoxCFrame, Vector3 BoxSize)
3380 >Returns the enclosing boundingbox of Box
3381
3382 table Region.FindAllPartsInRegion3(Region3 Region3, [Instance or table Ignore])
3383 >Returns all parts within a Region3 of any size
3384
3385 bool Region.BoxPointCollision(CFrame BoxCFrame, Vector3 BoxSize, Vector3 Point)
3386 >Returns true if the Point is intersecting the Box, false otherwise
3387
3388 bool Region.BoxSphereCollision(CFrame BoxCFrame, Vector3 BoxSize, Vector3 SphereCenter, number SphereRadius)
3389 >Returns true if the Sphere is intersecting the Box, false otherwise
3390
3391 bool Region.BoxCollision(CFrame Box0CFrame, Vector3 Box0Size, CFrame Box1CFrame, Vector3 Box1Size, [bool AssumeTrue])
3392 >Returns true if the boxes are intersecting, false otherwise
3393 If AssumeTrue is left blank, it does the full check to see if Box0 is intersecting Box1
3394 If AssumeTrue is true, it skips the heavy check and assumes that any part that could possibly be in the Region is
3395 If AssumeTrue is false, it skips the heavy check and assumes that any part that could possible be outside the Region is
3396
3397 bool Region.CastPoint(Vector3 Point, [Instance or table Ignore])
3398 >Returns true if the point intersects a part, false otherwise
3399]]
3400
3401local Region={}
3402
3403
3404
3405local BoxPointCollision do
3406 local VecDiv=CFrame.new().pointToObjectSpace--Right Division, yo.
3407 function BoxPointCollision(CFrame,Size,Point)
3408 local Relative =VecDiv(CFrame, Point)
3409 local sx,sy,sz =Size.x/2,Size.y/2,Size.z/2
3410 local rx,ry,rz =Relative.x,Relative.y,Relative.z
3411 return rx*rx<sx*sx and ry*ry<sy*sy and rz*rz<sz*sz
3412 end
3413end
3414
3415
3416
3417local BoxSphereCollision do
3418 local VecDiv=CFrame.new().pointToObjectSpace--Right Division, yo.
3419 function BoxSphereCollision(CFrame,Size,Center,Radius)
3420 local Relative =VecDiv(CFrame,Center)
3421 local sx,sy,sz =Size.x/2,Size.y/2,Size.z/2
3422 local rx,ry,rz =Relative.x,Relative.y,Relative.z
3423 local dx =rx>sx and rx-sx--Faster than if statement
3424 or rx<-sx and rx+sx
3425 or 0
3426 local dy =ry>sy and ry-sy
3427 or ry<-sy and ry+sy
3428 or 0
3429 local dz =rz>sz and rz-sz
3430 or rz<-sz and rz+sz
3431 or 0
3432 return dx*dx+dy*dy+dz*dz<Radius*Radius
3433 end
3434end
3435
3436
3437
3438--There's a reason why this hasn't been done before by ROBLOX users (as far as I know)
3439--It's really mathy, really long, and really confusing.
3440--0.000033 seconds is the worst, 0.000018 looks like the average case.
3441--Also I ran out of local variables so I had to redo everything so that I could reuse the names lol.
3442--So don't even try to read it.
3443local BoxCollision do
3444 local components=CFrame.new().components
3445 function BoxCollision(CFrame0,Size0,CFrame1,Size1,AssumeTrue)
3446 local m00,m01,m02,
3447 m03,m04,m05,
3448 m06,m07,m08,
3449 m09,m10,m11 =components(CFrame0)
3450 local m12,m13,m14,
3451 m15,m16,m17,
3452 m18,m19,m20,
3453 m21,m22,m23 =components(CFrame1)
3454 local m24,m25,m26 =Size0.x/2,Size0.y/2,Size0.z/2
3455 local m27,m28,m29 =Size1.x/2,Size1.y/2,Size1.z/2
3456 local m30,m31,m32 =m12-m00,m13-m01,m14-m02
3457 local m00 =m03*m30+m06*m31+m09*m32
3458 local m01 =m04*m30+m07*m31+m10*m32
3459 local m02 =m05*m30+m08*m31+m11*m32
3460 local m12 =m15*m30+m18*m31+m21*m32
3461 local m13 =m16*m30+m19*m31+m22*m32
3462 local m14 =m17*m30+m20*m31+m23*m32
3463 local m30 =m12>m27 and m12-m27
3464 or m12<-m27 and m12+m27
3465 or 0
3466 local m31 =m13>m28 and m13-m28
3467 or m13<-m28 and m13+m28
3468 or 0
3469 local m32 =m14>m29 and m14-m29
3470 or m14<-m29 and m14+m29
3471 or 0
3472 local m33 =m00>m24 and m00-m24
3473 or m00<-m24 and m00+m24
3474 or 0
3475 local m34 =m01>m25 and m01-m25
3476 or m01<-m25 and m01+m25
3477 or 0
3478 local m35 =m02>m26 and m02-m26
3479 or m02<-m26 and m02+m26
3480 or 0
3481 local m36 =m30*m30+m31*m31+m32*m32
3482 local m30 =m33*m33+m34*m34+m35*m35
3483 local m31 =m24<m25 and (m24<m26 and m24 or m26)
3484 or (m25<m26 and m25 or m26)
3485 local m32 =m27<m28 and (m27<m29 and m27 or m29)
3486 or (m28<m29 and m28 or m29)
3487 if m36<m31*m31 or m30<m32*m32 then
3488 return true
3489 elseif m36>m24*m24+m25*m25+m26*m26 or m30>m27*m27+m28*m28+m29*m29 then
3490 return false
3491 elseif AssumeTrue==nil then
3492 --LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL
3493 --(This is how you tell if something was made by Axis Angle)
3494 local m30=m03*m15+m06*m18+m09*m21
3495 local m31=m03*m16+m06*m19+m09*m22
3496 local m32=m03*m17+m06*m20+m09*m23
3497 local m03=m04*m15+m07*m18+m10*m21
3498 local m06=m04*m16+m07*m19+m10*m22
3499 local m09=m04*m17+m07*m20+m10*m23
3500 local m04=m05*m15+m08*m18+m11*m21
3501 local m07=m05*m16+m08*m19+m11*m22
3502 local m10=m05*m17+m08*m20+m11*m23
3503 local m05=m29*m29
3504 local m08=m27*m27
3505 local m11=m28*m28
3506 local m15=m24*m30
3507 local m16=m25*m03
3508 local m17=m26*m04
3509 local m18=m24*m31
3510 local m19=m25*m06
3511 local m20=m26*m07
3512 local m21=m24*m32
3513 local m22=m25*m09
3514 local m23=m26*m10
3515 local m33=m15+m16+m17-m12;if m33*m33<m08 then local m34=m18+m19+m20-m13;if m34*m34<m11 then local m35=m21+m22+m23-m14;if m35*m35<m05 then return true;end;end;end;
3516 local m33=-m15+m16+m17-m12;if m33*m33<m08 then local m34=-m18+m19+m20-m13;if m34*m34<m11 then local m35=-m21+m22+m23-m14;if m35*m35<m05 then return true;end;end;end;
3517 local m33=m15-m16+m17-m12;if m33*m33<m08 then local m34=m18-m19+m20-m13;if m34*m34<m11 then local m35=m21-m22+m23-m14;if m35*m35<m05 then return true;end;end;end;
3518 local m33=-m15-m16+m17-m12;if m33*m33<m08 then local m34=-m18-m19+m20-m13;if m34*m34<m11 then local m35=-m21-m22+m23-m14;if m35*m35<m05 then return true;end;end;end;
3519 local m33=m15+m16-m17-m12;if m33*m33<m08 then local m34=m18+m19-m20-m13;if m34*m34<m11 then local m35=m21+m22-m23-m14;if m35*m35<m05 then return true;end;end;end;
3520 local m33=-m15+m16-m17-m12;if m33*m33<m08 then local m34=-m18+m19-m20-m13;if m34*m34<m11 then local m35=-m21+m22-m23-m14;if m35*m35<m05 then return true;end;end;end;
3521 local m33=m15-m16-m17-m12;if m33*m33<m08 then local m34=m18-m19-m20-m13;if m34*m34<m11 then local m35=m21-m22-m23-m14;if m35*m35<m05 then return true;end;end;end;
3522 local m33=-m15-m16-m17-m12;if m33*m33<m08 then local m34=-m18-m19-m20-m13;if m34*m34<m11 then local m35=-m21-m22-m23-m14;if m35*m35<m05 then return true;end;end;end;
3523 local m12=m24*m24
3524 local m13=m25*m25
3525 local m14=m26*m26
3526 local m15=m27*m04
3527 local m16=m28*m07
3528 local m17=m27*m30
3529 local m18=m28*m31
3530 local m19=m27*m03
3531 local m20=m28*m06
3532 local m21=m29*m10
3533 local m22=m29*m32
3534 local m23=m29*m09
3535 local m35=(m02-m26+m15+m16)/m10;if m35*m35<m05 then local m33=m00+m17+m18-m35*m32;if m33*m33<m12 then local m34=m01+m19+m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3536 local m35=(m02+m26+m15+m16)/m10;if m35*m35<m05 then local m33=m00+m17+m18-m35*m32;if m33*m33<m12 then local m34=m01+m19+m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3537 local m35=(m02-m26-m15+m16)/m10;if m35*m35<m05 then local m33=m00-m17+m18-m35*m32;if m33*m33<m12 then local m34=m01-m19+m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3538 local m35=(m02+m26-m15+m16)/m10;if m35*m35<m05 then local m33=m00-m17+m18-m35*m32;if m33*m33<m12 then local m34=m01-m19+m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3539 local m35=(m02-m26+m15-m16)/m10;if m35*m35<m05 then local m33=m00+m17-m18-m35*m32;if m33*m33<m12 then local m34=m01+m19-m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3540 local m35=(m02+m26+m15-m16)/m10;if m35*m35<m05 then local m33=m00+m17-m18-m35*m32;if m33*m33<m12 then local m34=m01+m19-m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3541 local m35=(m02-m26-m15-m16)/m10;if m35*m35<m05 then local m33=m00-m17-m18-m35*m32;if m33*m33<m12 then local m34=m01-m19-m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3542 local m35=(m02+m26-m15-m16)/m10;if m35*m35<m05 then local m33=m00-m17-m18-m35*m32;if m33*m33<m12 then local m34=m01-m19-m20-m35*m09;if m34*m34<m13 then return true;end;end;end;
3543 local m35=(m00-m24+m17+m18)/m32;if m35*m35<m05 then local m33=m01+m19+m20-m35*m09;if m33*m33<m13 then local m34=m02+m15+m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3544 local m35=(m00+m24+m17+m18)/m32;if m35*m35<m05 then local m33=m01+m19+m20-m35*m09;if m33*m33<m13 then local m34=m02+m15+m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3545 local m35=(m00-m24-m17+m18)/m32;if m35*m35<m05 then local m33=m01-m19+m20-m35*m09;if m33*m33<m13 then local m34=m02-m15+m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3546 local m35=(m00+m24-m17+m18)/m32;if m35*m35<m05 then local m33=m01-m19+m20-m35*m09;if m33*m33<m13 then local m34=m02-m15+m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3547 local m35=(m00-m24+m17-m18)/m32;if m35*m35<m05 then local m33=m01+m19-m20-m35*m09;if m33*m33<m13 then local m34=m02+m15-m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3548 local m35=(m00+m24+m17-m18)/m32;if m35*m35<m05 then local m33=m01+m19-m20-m35*m09;if m33*m33<m13 then local m34=m02+m15-m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3549 local m35=(m00-m24-m17-m18)/m32;if m35*m35<m05 then local m33=m01-m19-m20-m35*m09;if m33*m33<m13 then local m34=m02-m15-m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3550 local m35=(m00+m24-m17-m18)/m32;if m35*m35<m05 then local m33=m01-m19-m20-m35*m09;if m33*m33<m13 then local m34=m02-m15-m16-m35*m10;if m34*m34<m14 then return true;end;end;end;
3551 local m35=(m01-m25+m19+m20)/m09;if m35*m35<m05 then local m33=m02+m15+m16-m35*m10;if m33*m33<m14 then local m34=m00+m17+m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3552 local m35=(m01+m25+m19+m20)/m09;if m35*m35<m05 then local m33=m02+m15+m16-m35*m10;if m33*m33<m14 then local m34=m00+m17+m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3553 local m35=(m01-m25-m19+m20)/m09;if m35*m35<m05 then local m33=m02-m15+m16-m35*m10;if m33*m33<m14 then local m34=m00-m17+m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3554 local m35=(m01+m25-m19+m20)/m09;if m35*m35<m05 then local m33=m02-m15+m16-m35*m10;if m33*m33<m14 then local m34=m00-m17+m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3555 local m35=(m01-m25+m19-m20)/m09;if m35*m35<m05 then local m33=m02+m15-m16-m35*m10;if m33*m33<m14 then local m34=m00+m17-m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3556 local m35=(m01+m25+m19-m20)/m09;if m35*m35<m05 then local m33=m02+m15-m16-m35*m10;if m33*m33<m14 then local m34=m00+m17-m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3557 local m35=(m01-m25-m19-m20)/m09;if m35*m35<m05 then local m33=m02-m15-m16-m35*m10;if m33*m33<m14 then local m34=m00-m17-m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3558 local m35=(m01+m25-m19-m20)/m09;if m35*m35<m05 then local m33=m02-m15-m16-m35*m10;if m33*m33<m14 then local m34=m00-m17-m18-m35*m32;if m34*m34<m12 then return true;end;end;end;
3559 local m35=(m02-m26+m16+m21)/m04;if m35*m35<m08 then local m33=m00+m18+m22-m35*m30;if m33*m33<m12 then local m34=m01+m20+m23-m35*m03;if m34*m34<m13 then return true;end;end;end;
3560 local m35=(m02+m26+m16+m21)/m04;if m35*m35<m08 then local m33=m00+m18+m22-m35*m30;if m33*m33<m12 then local m34=m01+m20+m23-m35*m03;if m34*m34<m13 then return true;end;end;end;
3561 local m35=(m02-m26-m16+m21)/m04;if m35*m35<m08 then local m33=m00-m18+m22-m35*m30;if m33*m33<m12 then local m34=m01-m20+m23-m35*m03;if m34*m34<m13 then return true;end;end;end;
3562 local m35=(m02+m26-m16+m21)/m04;if m35*m35<m08 then local m33=m00-m18+m22-m35*m30;if m33*m33<m12 then local m34=m01-m20+m23-m35*m03;if m34*m34<m13 then return true;end;end;end;
3563 local m35=(m02-m26+m16-m21)/m04;if m35*m35<m08 then local m33=m00+m18-m22-m35*m30;if m33*m33<m12 then local Axi=m01+m20-m23-m35*m03;if Axi*Axi<m13 then return true;end;end;end;
3564 local m35=(m02+m26+m16-m21)/m04;if m35*m35<m08 then local m33=m00+m18-m22-m35*m30;if m33*m33<m12 then local sAn=m01+m20-m23-m35*m03;if sAn*sAn<m13 then return true;end;end;end;
3565 local m35=(m02-m26-m16-m21)/m04;if m35*m35<m08 then local m33=m00-m18-m22-m35*m30;if m33*m33<m12 then local gle=m01-m20-m23-m35*m03;if gle*gle<m13 then return true;end;end;end;
3566 local m35=(m02+m26-m16-m21)/m04;if m35*m35<m08 then local m33=m00-m18-m22-m35*m30;if m33*m33<m12 then local m34=m01-m20-m23-m35*m03;if m34*m34<m13 then return true;end;end;end;
3567 local m35=(m00-m24+m18+m22)/m30;if m35*m35<m08 then local m33=m01+m20+m23-m35*m03;if m33*m33<m13 then local m34=m02+m16+m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3568 local m35=(m00+m24+m18+m22)/m30;if m35*m35<m08 then local m33=m01+m20+m23-m35*m03;if m33*m33<m13 then local m34=m02+m16+m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3569 local m35=(m00-m24-m18+m22)/m30;if m35*m35<m08 then local m33=m01-m20+m23-m35*m03;if m33*m33<m13 then local m34=m02-m16+m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3570 local m35=(m00+m24-m18+m22)/m30;if m35*m35<m08 then local m33=m01-m20+m23-m35*m03;if m33*m33<m13 then local m34=m02-m16+m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3571 local m35=(m00-m24+m18-m22)/m30;if m35*m35<m08 then local m33=m01+m20-m23-m35*m03;if m33*m33<m13 then local m34=m02+m16-m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3572 local m35=(m00+m24+m18-m22)/m30;if m35*m35<m08 then local m33=m01+m20-m23-m35*m03;if m33*m33<m13 then local m34=m02+m16-m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3573 local m35=(m00-m24-m18-m22)/m30;if m35*m35<m08 then local m33=m01-m20-m23-m35*m03;if m33*m33<m13 then local m34=m02-m16-m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3574 local m35=(m00+m24-m18-m22)/m30;if m35*m35<m08 then local m33=m01-m20-m23-m35*m03;if m33*m33<m13 then local m34=m02-m16-m21-m35*m04;if m34*m34<m14 then return true;end;end;end;
3575 local m35=(m01-m25+m20+m23)/m03;if m35*m35<m08 then local m33=m02+m16+m21-m35*m04;if m33*m33<m14 then local m34=m00+m18+m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3576 local m35=(m01+m25+m20+m23)/m03;if m35*m35<m08 then local m33=m02+m16+m21-m35*m04;if m33*m33<m14 then local m34=m00+m18+m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3577 local m35=(m01-m25-m20+m23)/m03;if m35*m35<m08 then local m33=m02-m16+m21-m35*m04;if m33*m33<m14 then local m34=m00-m18+m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3578 local m35=(m01+m25-m20+m23)/m03;if m35*m35<m08 then local m33=m02-m16+m21-m35*m04;if m33*m33<m14 then local m34=m00-m18+m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3579 local m35=(m01-m25+m20-m23)/m03;if m35*m35<m08 then local m33=m02+m16-m21-m35*m04;if m33*m33<m14 then local m34=m00+m18-m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3580 local m35=(m01+m25+m20-m23)/m03;if m35*m35<m08 then local m33=m02+m16-m21-m35*m04;if m33*m33<m14 then local m34=m00+m18-m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3581 local m35=(m01-m25-m20-m23)/m03;if m35*m35<m08 then local m33=m02-m16-m21-m35*m04;if m33*m33<m14 then local m34=m00-m18-m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3582 local m35=(m01+m25-m20-m23)/m03;if m35*m35<m08 then local m33=m02-m16-m21-m35*m04;if m33*m33<m14 then local m34=m00-m18-m22-m35*m30;if m34*m34<m12 then return true;end;end;end;
3583 local m35=(m02-m26+m21+m15)/m07;if m35*m35<m11 then local m33=m00+m22+m17-m35*m31;if m33*m33<m12 then local m34=m01+m23+m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3584 local m35=(m02+m26+m21+m15)/m07;if m35*m35<m11 then local m33=m00+m22+m17-m35*m31;if m33*m33<m12 then local m34=m01+m23+m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3585 local m35=(m02-m26-m21+m15)/m07;if m35*m35<m11 then local m33=m00-m22+m17-m35*m31;if m33*m33<m12 then local m34=m01-m23+m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3586 local m35=(m02+m26-m21+m15)/m07;if m35*m35<m11 then local m33=m00-m22+m17-m35*m31;if m33*m33<m12 then local m34=m01-m23+m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3587 local m35=(m02-m26+m21-m15)/m07;if m35*m35<m11 then local m33=m00+m22-m17-m35*m31;if m33*m33<m12 then local m34=m01+m23-m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3588 local m35=(m02+m26+m21-m15)/m07;if m35*m35<m11 then local m33=m00+m22-m17-m35*m31;if m33*m33<m12 then local m34=m01+m23-m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3589 local m35=(m02-m26-m21-m15)/m07;if m35*m35<m11 then local m33=m00-m22-m17-m35*m31;if m33*m33<m12 then local m34=m01-m23-m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3590 local m35=(m02+m26-m21-m15)/m07;if m35*m35<m11 then local m33=m00-m22-m17-m35*m31;if m33*m33<m12 then local m34=m01-m23-m19-m35*m06;if m34*m34<m13 then return true;end;end;end;
3591 local m35=(m00-m24+m22+m17)/m31;if m35*m35<m11 then local m33=m01+m23+m19-m35*m06;if m33*m33<m13 then local m34=m02+m21+m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3592 local m35=(m00+m24+m22+m17)/m31;if m35*m35<m11 then local m33=m01+m23+m19-m35*m06;if m33*m33<m13 then local m34=m02+m21+m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3593 local m35=(m00-m24-m22+m17)/m31;if m35*m35<m11 then local m33=m01-m23+m19-m35*m06;if m33*m33<m13 then local m34=m02-m21+m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3594 local m35=(m00+m24-m22+m17)/m31;if m35*m35<m11 then local m33=m01-m23+m19-m35*m06;if m33*m33<m13 then local m34=m02-m21+m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3595 local m35=(m00-m24+m22-m17)/m31;if m35*m35<m11 then local m33=m01+m23-m19-m35*m06;if m33*m33<m13 then local m34=m02+m21-m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3596 local m35=(m00+m24+m22-m17)/m31;if m35*m35<m11 then local m33=m01+m23-m19-m35*m06;if m33*m33<m13 then local m34=m02+m21-m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3597 local m35=(m00-m24-m22-m17)/m31;if m35*m35<m11 then local m33=m01-m23-m19-m35*m06;if m33*m33<m13 then local m34=m02-m21-m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3598 local m35=(m00+m24-m22-m17)/m31;if m35*m35<m11 then local m33=m01-m23-m19-m35*m06;if m33*m33<m13 then local m34=m02-m21-m15-m35*m07;if m34*m34<m14 then return true;end;end;end;
3599 local m35=(m01-m25+m23+m19)/m06;if m35*m35<m11 then local m33=m02+m21+m15-m35*m07;if m33*m33<m14 then local m34=m00+m22+m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3600 local m35=(m01+m25+m23+m19)/m06;if m35*m35<m11 then local m33=m02+m21+m15-m35*m07;if m33*m33<m14 then local m34=m00+m22+m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3601 local m35=(m01-m25-m23+m19)/m06;if m35*m35<m11 then local m33=m02-m21+m15-m35*m07;if m33*m33<m14 then local m34=m00-m22+m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3602 local m35=(m01+m25-m23+m19)/m06;if m35*m35<m11 then local m33=m02-m21+m15-m35*m07;if m33*m33<m14 then local m34=m00-m22+m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3603 local m35=(m01-m25+m23-m19)/m06;if m35*m35<m11 then local m33=m02+m21-m15-m35*m07;if m33*m33<m14 then local m34=m00+m22-m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3604 local m35=(m01+m25+m23-m19)/m06;if m35*m35<m11 then local m33=m02+m21-m15-m35*m07;if m33*m33<m14 then local m34=m00+m22-m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3605 local m35=(m01-m25-m23-m19)/m06;if m35*m35<m11 then local m33=m02-m21-m15-m35*m07;if m33*m33<m14 then local m34=m00-m22-m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3606 local m35=(m01+m25-m23-m19)/m06;if m35*m35<m11 then local m33=m02-m21-m15-m35*m07;if m33*m33<m14 then local m34=m00-m22-m17-m35*m31;if m34*m34<m12 then return true;end;end;end;
3607 return false
3608 else
3609 return AssumeTrue
3610 end
3611 end
3612end
3613
3614
3615local setmetatable =setmetatable
3616local components =CFrame.new().components
3617local Workspace =Workspace
3618local BoxCast =Workspace.FindPartsInRegion3WithIgnoreList
3619local unpack =unpack
3620local type =type
3621local IsA =game.IsA
3622local r3 =Region3.new
3623local v3 =Vector3.new
3624
3625
3626
3627local function Region3BoundingBox(CFrame,Size)
3628 local x,y,z,
3629 xx,yx,zx,
3630 xy,yy,zy,
3631 xz,yz,zz=components(CFrame)
3632 local sx,sy,sz=Size.x/2,Size.y/2,Size.z/2
3633 local px =sx*(xx<0 and -xx or xx)
3634 +sy*(yx<0 and -yx or yx)
3635 +sz*(zx<0 and -zx or zx)
3636 local py =sx*(xy<0 and -xy or xy)
3637 +sy*(yy<0 and -yy or yy)
3638 +sz*(zy<0 and -zy or zy)
3639 local pz =sx*(xz<0 and -xz or xz)
3640 +sy*(yz<0 and -yz or yz)
3641 +sz*(zz<0 and -zz or zz)
3642 return r3(v3(x-px,y-py,z-pz),v3(x+px,y+py,z+pz))
3643end
3644
3645
3646
3647local function FindAllPartsInRegion3(Region3,Ignore)
3648 local Ignore=type(Ignore)=="table" and Ignore or {Ignore}
3649 local Last=#Ignore
3650 repeat
3651 local Parts=BoxCast(Workspace,Region3,Ignore,100)
3652 local Start=#Ignore
3653 for i=1,#Parts do
3654 Ignore[Start+i]=Parts[i]
3655 end
3656 until #Parts<100;
3657 return {unpack(Ignore,Last+1,#Ignore)}
3658end
3659
3660
3661
3662local function CastPoint(Region,Point)
3663 return BoxPointCollision(Region.CFrame,Region.Size,Point)
3664end
3665
3666
3667
3668local function CastSphere(Region,Center,Radius)
3669 return BoxSphereCollision(Region.CFrame,Region.Size,Center,Radius)
3670end
3671
3672
3673
3674local function CastBox(Region,CFrame,Size)
3675 return BoxCollision(Region.CFrame,Region.Size,CFrame,Size)
3676end
3677
3678
3679
3680local function CastPart(Region,Part)
3681 local result = BoxCollision(Region.CFrame,Region.Size,Part.CFrame,Part.Size);
3682 return result;
3683end
3684
3685
3686
3687local function CastParts(Region,Parts)
3688 local Inside={}
3689 for i=1,#Parts do
3690 if CastPart(Region,Parts[i]) then
3691 Inside[#Inside+1]=Parts[i]
3692 end
3693 end
3694 return Inside
3695end
3696
3697
3698
3699local function Cast(Region,Ignore)
3700 local Inside={}
3701 local Parts=FindAllPartsInRegion3(Region.Region3,Ignore)
3702 for i=1,#Parts do
3703 if CastPart(Region,Parts[i]) then
3704 Inside[#Inside+1]=Parts[i]
3705 end
3706 end
3707 return Inside
3708end
3709
3710
3711
3712local function NewRegion(CFrame,Size)
3713 local Object ={
3714 CFrame =CFrame;
3715 Size =Size;
3716 Region3 =Region3BoundingBox(CFrame,Size);
3717 Cast =Cast;
3718 CastPart =CastPart;
3719 CastParts =CastParts;
3720 CastPoint =CastPoint;
3721 CastSphere =CastSphere;
3722 CastBox =CastBox;
3723 }
3724 return setmetatable({},{
3725 __index=Object;
3726 __newindex=function(_,Index,Value)
3727 Object[Index]=Value
3728 Object.Region3=Region3BoundingBox(Object.CFrame,Object.Size)
3729 end;
3730 })
3731end
3732
3733
3734
3735Region.Region3BoundingBox =Region3BoundingBox
3736Region.FindAllPartsInRegion3=FindAllPartsInRegion3
3737Region.BoxPointCollision =BoxPointCollision
3738Region.BoxSphereCollision =BoxSphereCollision
3739Region.BoxCollision =BoxCollision
3740Region.new =NewRegion
3741function Region.FromPart(Part)
3742 return NewRegion(Part.CFrame,Part.Size)
3743end
3744
3745return Region\
3746-- Services
3747MarketplaceService = Game:GetService 'MarketplaceService';
3748HttpService = Game:GetService 'HttpService';
3749Workspace = Game:GetService 'Workspace';
3750
3751-- References
3752Tool = script.Parent;
3753Support = require(Tool:WaitForChild 'SupportLibrary');
3754RegionModule = require(Tool:WaitForChild 'Region by AxisAngle');
3755
3756-- Initialize the security module
3757Security = {};
3758
3759-- The distance above the area-defining part that counts as part of the area
3760Security.AreaHeight = 500;
3761
3762-- Whether to allow building outside of private areas
3763Security.AllowPublicBuilding = true;
3764
3765-- Allowed locations in the hierarchy (descendants of which are authorized)
3766Security.AllowedLocations = { Workspace };
3767
3768-- Track the enabling of areas
3769Security.Areas = Workspace:FindFirstChild('[Private Building Areas]');
3770Workspace.ChildAdded:connect(function (Child)
3771 if not Security.Areas and Child.Name == '[Private Building Areas]' then
3772 Security.Areas = Child;
3773 end;
3774end);
3775Workspace.ChildRemoved:connect(function (Child)
3776 if Security.Areas and Child.Name == '[Private Building Areas]' then
3777 Security.Areas = nil;
3778 end;
3779end);
3780
3781function Security.IsAreaAuthorizedForPlayer(Area, Player)
3782 -- Returns whether `Player` has permission to manipulate parts in this area
3783
3784 -- Ensure area has permissions
3785 local Permissions = Area:FindFirstChild '[Permissions]';
3786 if not Permissions then
3787 return;
3788 else
3789 Permissions = require(Permissions);
3790 end;
3791
3792 -- Ensure permissions are set up
3793 if not Permissions then
3794 return;
3795 end;
3796
3797 -- Search for authorizing permission
3798 for _, Permission in pairs(Permissions) do
3799
3800 -- Check group permissions
3801 if Permission.Type == 'Group' then
3802
3803 -- Check player's group membership
3804 local PlayerInGroup = Player:IsInGroup(Permission.GroupId);
3805
3806 -- If no specific rank is required, authorize
3807 if PlayerInGroup and not Permission.Ranks then
3808 return true;
3809
3810 -- If specific rank is required, check player rank
3811 elseif PlayerInGroup and Permission.Ranks then
3812 local Symbol, RankNumber = tostring(Permission.Ranks):match('([<>]?=?)([0-9]+)');
3813 local PlayerRank = Player:GetRankInGroup(Permission.GroupId);
3814 RankNumber = tonumber(RankNumber);
3815
3816 -- Check the player rank
3817 if not Symbol and (PlayerRank == RankNumber) then
3818 return true;
3819 elseif Symbol == '=' and (PlayerRank == RankNumber) then
3820 return true;
3821 elseif Symbol == '>' and (PlayerRank > RankNumber) then
3822 return true;
3823 elseif Symbol == '<' and (PlayerRank < RankNumber) then
3824 return true;
3825 elseif Symbol == '>=' and (PlayerRank >= RankNumber) then
3826 return true;
3827 elseif Symbol == '<=' and (PlayerRank <= RankNumber) then
3828 return true;
3829 end;
3830 end;
3831
3832 -- Check player permissions
3833 elseif Permission.Type == 'Player' then
3834 if (Player.userId == Permission.PlayerId) or (Player.Name == Permission.PlayerName) then
3835 return true;
3836 end;
3837
3838 -- Check owner permissions
3839 elseif Permission.Type == 'Owner' then
3840 if (Player.userId == Permission.PlayerId) or (Player.Name == Permission.PlayerName) then
3841 return true;
3842 end;
3843
3844 -- Check auto-permissions
3845 elseif Permission.Type == 'Anybody' then
3846 return true;
3847
3848 -- Check friend permissions
3849 elseif Permission.Type == 'Friends' then
3850 if Player:IsFriendsWith(Permission.PlayerId) then
3851 return true;
3852 end;
3853
3854 -- Check asset permissions
3855 elseif Permission.Type == 'Asset' then
3856 if MarketplaceService:PlayerOwnsAsset(Player, Permission.AssetId) then
3857 return true;
3858 end;
3859
3860 -- Check team permissions
3861 elseif Permission.Type == 'Team' then
3862 if Permission.Team and Player.Team == Permission.Team then
3863 return true;
3864 elseif Permission.TeamColor and Player.Team and Player.Team.TeamColor == Permission.TeamColor then
3865 return true;
3866 elseif Permission.TeamName and Player.Team and Player.Team.Name == Permission.TeamName then
3867 return true;
3868 end;
3869
3870 -- Check BC permissions
3871 elseif Permission.Type == 'NoBC' then
3872 if Player.MembershipType == Enum.MembershipType.None then
3873 return true;
3874 end;
3875 elseif Permission.Type == 'AnyBC' then
3876 if Player.MembershipType ~= Enum.MembershipType.None then
3877 return true;
3878 end;
3879 elseif Permission.Type == 'BC' then
3880 if Player.MembershipType == Enum.MembershipType.BuildersClub then
3881 return true;
3882 end;
3883 elseif Permission.Type == 'TBC' then
3884 if Player.MembershipType == Enum.MembershipType.TurboBuildersClub then
3885 return true;
3886 end;
3887 elseif Permission.Type == 'OBC' then
3888 if Player.MembershipType == Enum.MembershipType.OutrageousBuildersClub then
3889 return true;
3890 end;
3891
3892 -- Check custom permissions
3893 elseif Permission.Type == 'Callback' then
3894 return Permission.Callback(Player);
3895 end;
3896
3897 end;
3898
3899 -- If the player passes none of these conditions, deny access
3900 return false;
3901end;
3902
3903function Security.IsItemAllowed(Item)
3904 -- Returns whether instance `Item` can be accessed
3905
3906 -- Ensure `Item` is a part or a model
3907 if not Item:IsA 'BasePart' and not Item:IsA 'Model' then
3908 return false;
3909 end;
3910
3911 -- Check if `Item` descendants from any allowed location
3912 for _, AllowedLocation in pairs(Security.AllowedLocations) do
3913 if Item:IsDescendantOf(AllowedLocation) then
3914 return true;
3915 end;
3916 end;
3917
3918 -- Deny if `Item` is not a descendant of any allowed location
3919 return false;
3920
3921end;
3922
3923function Security.AreAreasEnabled()
3924 -- Returns whether areas are enabled
3925
3926 -- Base whether areas are enabled depending on area container presence and tool mode
3927 if Security.Areas and Tool.ClassName == 'Tool' then
3928 return true;
3929 else
3930 return false;
3931 end;
3932end;
3933
3934function Security.ArePartsViolatingAreas(Parts, Player, ExemptPartial, AreaPermissions)
3935 -- Returns whether the given parts are inside any unauthorized areas
3936
3937 -- Make sure area security is being enforced
3938 if not Security.AreAreasEnabled() then
3939 return false;
3940 end;
3941
3942 -- Make sure there is a permissions cache
3943 AreaPermissions = AreaPermissions or {};
3944
3945 -- Check which areas the parts are in
3946 local Areas, AreaMap = Security.GetSelectionAreas(Parts, not ExemptPartial and not Security.AllowPublicBuilding);
3947
3948 -- Check authorization for each relevant area
3949 for _, Area in pairs(Areas) do
3950
3951 -- Determine authorization if not in given permissions cache
3952 if AreaPermissions[Area] == nil then
3953 AreaPermissions[Area] = Security.IsAreaAuthorizedForPlayer(Area, Player);
3954 end;
3955
3956 -- If unauthorized and partial violations aren't exempt, declare violation
3957 if not ExemptPartial and AreaPermissions[Area] == false then
3958 return true;
3959 end;
3960
3961 -- If authorized, and partial violations are exempt, call off any violation
3962 if ExemptPartial and AreaPermissions[Area] then
3963 return false;
3964 end;
3965
3966 end;
3967
3968 -- If not in a private area, determine violation based on public building policy
3969 if #Areas == 0 then
3970 return not Security.AllowPublicBuilding;
3971
3972 -- If authorization for a partial violation-exempt check on an area failed, indicate a violation
3973 elseif ExemptPartial then
3974 return true;
3975
3976 -- If in authorized areas, determine violation based on public building policy compliance
3977 elseif AreaMap and not Security.AllowPublicBuilding then
3978
3979 -- Check area residence of each part's corner
3980 local PartCornerCompliance = {};
3981 for AreaRegion, Parts in pairs(AreaMap) do
3982 for _, Part in pairs(Parts) do
3983 PartCornerCompliance[Part] = PartCornerCompliance[Part] or 0;
3984
3985 -- Track the number of corners that `Part` has in this region
3986 for _, Corner in pairs(Support.GetPartCorners(Part)) do
3987 if AreaRegion:CastPoint(Corner.p) then
3988 PartCornerCompliance[Part] = PartCornerCompliance[Part] + 1;
3989 end;
3990 end;
3991
3992 end;
3993 end;
3994
3995 -- Ensure all corners of the part are contained within areas
3996 for _, CornersContained in pairs(PartCornerCompliance) do
3997 if CornersContained ~= 8 then
3998 return true;
3999 end;
4000 end;
4001
4002 end;
4003
4004 -- If no violations occur, indicate no violations
4005 return false;
4006end;
4007
4008function Security.GetSelectionAreas(Selection, ReturnMap)
4009 -- Returns a list of areas that the selection of parts is in
4010
4011 -- Make sure areas are enabled
4012 if not Security.AreAreasEnabled() then
4013 return {};
4014 end;
4015
4016 -- Start a map if requested
4017 local Map = ReturnMap and {} or nil;
4018
4019 -- Check each area to find out if any of the parts are within
4020 local Areas = {};
4021 for _, Area in pairs(Security.Areas:GetChildren()) do
4022
4023 -- Get all parts from the selection within this area
4024 local Region = RegionModule.new(
4025 Area.CFrame * CFrame.new(0, Security.AreaHeight / 2 - Area.Size.Y / 2, 0),
4026 Vector3.new(Area.Size.X, Security.AreaHeight + Area.Size.Y, Area.Size.Z)
4027 );
4028 local ContainedParts = Region:CastParts(Selection);
4029
4030 -- If parts are in this area, remember the area
4031 if #ContainedParts > 0 then
4032 table.insert(Areas, Area);
4033
4034 -- Map out the parts for each area region
4035 if Map then
4036 Map[Region] = ContainedParts;
4037 end;
4038 end;
4039
4040 end;
4041
4042 -- Return the areas where any of the given parts exist
4043 return Areas, Map;
4044end;
4045
4046function Security.GetPermissions(Areas, Player)
4047 -- Returns a cache of the current player's authorization to the given areas
4048
4049 -- Make sure security is enabled
4050 if not Security.AreAreasEnabled() then
4051 return;
4052 end;
4053
4054 -- Build the cache of permissions for each area
4055 local Cache = {};
4056 for _, Area in pairs(Areas) do
4057 Cache[Area] = Security.IsAreaAuthorizedForPlayer(Area, Player);
4058 end;
4059
4060 -- Return the permissions cache
4061 return Cache;
4062end;
4063
4064return Security;
4065-- Libraries
4066local RbxUtility = LoadLibrary 'RbxUtility';
4067local History = require(script.Parent.HistoryModule);
4068local Support = require(script.Parent.SupportLibrary);
4069
4070-- Core selection system
4071Selection = {};
4072Selection.Items = {};
4073Selection.ItemIndex = {};
4074Selection.Outlines = {};
4075Selection.Color = BrickColor.new 'Cyan';
4076Selection.Multiselecting = false;
4077
4078-- Events to listen to selection changes
4079Selection.ItemsAdded = RbxUtility.CreateSignal();
4080Selection.ItemsRemoved = RbxUtility.CreateSignal();
4081Selection.FocusChanged = RbxUtility.CreateSignal();
4082Selection.Cleared = RbxUtility.CreateSignal();
4083Selection.Changed = RbxUtility.CreateSignal();
4084
4085-- Item existence listeners
4086local Listeners = {};
4087
4088function Selection.IsSelected(Item)
4089 -- Returns whether `Item` is selected or not
4090
4091 -- Check and return item presence in index
4092 return Selection.ItemIndex[Item];
4093
4094end;
4095
4096function Selection.Add(Items, RegisterHistory)
4097 -- Adds the given items to the selection
4098
4099 -- Get core API
4100 local Core = GetCore();
4101
4102 -- Go through and validate each given item
4103 local SelectableItems = {};
4104 for _, Item in pairs(Items) do
4105
4106 -- Make sure each item is valid and not already selected
4107 if Core.IsSelectable(Item) and not Selection.ItemIndex[Item] then
4108 table.insert(SelectableItems, Item);
4109 end;
4110
4111 end;
4112
4113 local OldSelection = Selection.Items;
4114
4115 -- Go through the valid new selection items
4116 for _, Item in pairs(SelectableItems) do
4117
4118 -- Add each valid item to the selection
4119 Selection.ItemIndex[Item] = true;
4120
4121 -- Deselect items that are destroyed
4122 Listeners[Item] = Item.AncestryChanged:connect(function (Object, Parent)
4123 if Parent == nil then
4124 Selection.Remove({ Item });
4125 end;
4126 end);
4127
4128 end;
4129
4130 -- Update selected item list
4131 Selection.Items = Support.Keys(Selection.ItemIndex);
4132
4133 -- Create a history record for this selection change, if requested
4134 if RegisterHistory and #SelectableItems > 0 then
4135 TrackSelectionChange(OldSelection);
4136 end;
4137
4138 -- Create selection boxes for the selection
4139 CreateSelectionBoxes(SelectableItems);
4140
4141 -- Fire relevant events
4142 Selection.ItemsAdded:fire(SelectableItems);
4143 Selection.Changed:fire();
4144
4145end;
4146
4147function Selection.Remove(Items, RegisterHistory)
4148 -- Removes the given items from the selection
4149
4150 -- Go through and validate each given item
4151 local DeselectableItems = {};
4152 for _, Item in pairs(Items) do
4153
4154 -- Make sure each item is actually selected
4155 if Selection.IsSelected(Item) then
4156 table.insert(DeselectableItems, Item);
4157 end;
4158
4159 end;
4160
4161 local OldSelection = Selection.Items;
4162
4163 -- Go through the valid deselectable items
4164 for _, Item in pairs(DeselectableItems) do
4165
4166 -- Remove item from selection
4167 Selection.ItemIndex[Item] = nil;
4168
4169 -- Stop tracking item's parent
4170 Listeners[Item]:disconnect();
4171 Listeners[Item] = nil;
4172
4173 end;
4174
4175 -- Remove selection boxes from deselected items
4176 RemoveSelectionBoxes(DeselectableItems);
4177
4178 -- Update selected item list
4179 Selection.Items = Support.Keys(Selection.ItemIndex);
4180
4181 -- Create a history record for this selection change, if requested
4182 if RegisterHistory and #DeselectableItems > 0 then
4183 TrackSelectionChange(OldSelection);
4184 end;
4185
4186 -- Fire relevant events
4187 Selection.ItemsRemoved:fire(DeselectableItems);
4188 Selection.Changed:fire();
4189
4190end;
4191
4192function Selection.Clear(RegisterHistory)
4193 -- Clears all items from selection
4194
4195 -- Remove all selected items
4196 Selection.Remove(Selection.Items, RegisterHistory);
4197
4198 -- Fire relevant events
4199 Selection.Cleared:fire();
4200
4201end;
4202
4203function Selection.Replace(Items, RegisterHistory)
4204 -- Replaces the current selection with the given new items
4205
4206 -- Save old selection reference for history
4207 local OldSelection = Selection.Items;
4208
4209 -- Clear current selection and select new items
4210 Selection.Clear(false);
4211 Selection.Add(Items, false);
4212
4213 -- Create a history record for this selection change, if requested
4214 if RegisterHistory then
4215 TrackSelectionChange(OldSelection);
4216 end;
4217
4218end;
4219
4220function Selection.SetFocus(Item)
4221 -- Selects `Item` as the focused selection item
4222
4223 -- Make sure the item is selected or is `nil`
4224 if not Selection.IsSelected(Item) and Item ~= nil then
4225 return;
4226 end;
4227
4228 -- Set the item as the focus
4229 Selection.Focus = Item;
4230
4231 -- Fire relevant events
4232 Selection.FocusChanged:fire(Item);
4233
4234end;
4235
4236function FocusOnLastSelectedPart()
4237 -- Sets the last part of the selection as the focus
4238
4239 -- If selection is empty, clear the focus
4240 if #Selection.Items == 0 then
4241 Selection.SetFocus(nil);
4242
4243 -- Otherwise, focus on the last part in the selection
4244 else
4245 Selection.SetFocus(Selection.Items[#Selection.Items]);
4246 end;
4247
4248end;
4249
4250-- Listen for changes to the selection and keep the focus updated
4251Selection.Changed:connect(FocusOnLastSelectedPart);
4252
4253function GetCore()
4254 -- Returns the core API
4255 return require(script.Parent.Core);
4256end;
4257
4258function CreateSelectionBoxes(Items)
4259 -- Creates a SelectionBox for each given item
4260
4261 -- Get the core API
4262 local Core = GetCore();
4263
4264 -- Only create selection boxes if in tool mode
4265 if Core.Mode ~= 'Tool' then
4266 return;
4267 end;
4268
4269 -- Track new selection boxes
4270 local SelectionBoxes = {};
4271
4272 -- Create an outline for each part
4273 for _, Item in pairs(Items) do
4274
4275 -- Avoid duplicate selection boxes
4276 if not Selection.Outlines[Item] then
4277
4278 -- Create the selection box
4279 local SelectionBox = Instance.new 'SelectionBox';
4280 SelectionBox.Name = 'BTSelectionBox';
4281 SelectionBox.Color = Selection.Color;
4282 SelectionBox.Adornee = Item;
4283 SelectionBox.LineThickness = 0.025;
4284 SelectionBox.Transparency = 0.5;
4285
4286 -- Register the outline
4287 Selection.Outlines[Item] = SelectionBox;
4288 table.insert(SelectionBoxes, SelectionBox);
4289
4290 end;
4291
4292 end;
4293
4294 -- Parent the selection boxes
4295 for _, SelectionBox in pairs(SelectionBoxes) do
4296 SelectionBox.Parent = Core.UIContainer;
4297 end;
4298
4299end;
4300
4301function RemoveSelectionBoxes(Items)
4302 -- Removes the given item's selection box
4303
4304 -- Only proceed if in tool mode
4305 if GetCore().Mode ~= 'Tool' then
4306 return;
4307 end;
4308
4309 -- Remove each item's outline
4310 for _, Item in pairs(Items) do
4311
4312 -- Get the item's selection box
4313 local SelectionBox = Selection.Outlines[Item];
4314
4315 -- Remove the selection box if found
4316 if SelectionBox then
4317 SelectionBox:Destroy();
4318 end;
4319
4320 -- Deregister the selection box
4321 Selection.Outlines[Item] = nil;
4322
4323 end;
4324
4325end;
4326
4327function Selection.RecolorOutlines(Color)
4328 -- Updates selection outline colors
4329
4330 -- Set `Color` as the new color
4331 Selection.Color = Color;
4332
4333 -- Recolor existing outlines
4334 for _, Outline in pairs(Selection.Outlines) do
4335 Outline.Color = Selection.Color;
4336 end;
4337
4338end;
4339
4340function Selection.FlashOutlines()
4341 -- Flashes selection outlines for emphasis
4342
4343 -- Fade in from complete to normal transparency
4344 for Transparency = 1, 0.5, -0.1 do
4345
4346 -- Update each outline
4347 for _, Outline in pairs(Selection.Outlines) do
4348 Outline.Transparency = Transparency;
4349 end;
4350
4351 -- Fade over time
4352 wait(0.1);
4353
4354 end;
4355
4356end;
4357
4358function Selection.EnableOutlines()
4359 -- Shows selection outlines
4360
4361 local UIContainer = GetCore().UIContainer;
4362
4363 -- Show each outline
4364 for _, Outline in pairs(Selection.Outlines) do
4365 Outline.Parent = UIContainer;
4366 end;
4367
4368 -- Hide outlines when tool is disabled
4369 GetCore().Connections.HideOutlinesOnDisable = GetCore().Disabling:connect(Selection.HideOutlines);
4370
4371end;
4372
4373function Selection.EnableMultiselectionHotkeys()
4374 -- Enables hotkeys for multiselecting
4375
4376 -- Determine multiselection hotkeys
4377 local Hotkeys = Support.FlipTable { 'LeftShift', 'RightShift', 'LeftControl', 'RightControl' };
4378
4379 -- Get core API
4380 local Core = GetCore();
4381
4382 -- Listen for matching key presses
4383 Core.Connections.MultiselectionHotkeys = Support.AddUserInputListener('Began', 'Keyboard', false, function (Input)
4384 if Hotkeys[Input.KeyCode.Name] then
4385 Selection.Multiselecting = true;
4386 end;
4387 end);
4388
4389 -- Listen for matching key releases
4390 Core.Connections.MultiselectingReleaseHotkeys = Support.AddUserInputListener('Ended', 'Keyboard', true, function (Input)
4391
4392 -- Get currently pressed keys
4393 local PressedKeys = Support.GetListMembers(Support.GetListMembers(Game:GetService('UserInputService'):GetKeysPressed(), 'KeyCode'), 'Name');
4394
4395 -- Continue multiselection if a hotkey is still pressed
4396 for _, PressedKey in pairs(PressedKeys) do
4397 if Hotkeys[PressedKey] then
4398 return;
4399 end;
4400 end;
4401
4402 -- Disable multiselection if matching key not found
4403 Selection.Multiselecting = false;
4404
4405 end);
4406
4407end;
4408
4409function Selection.HideOutlines()
4410 -- Hides selection outlines
4411
4412 -- Hide each outline
4413 for _, Outline in pairs(Selection.Outlines) do
4414 Outline.Parent = nil;
4415 end;
4416
4417end;
4418
4419function TrackSelectionChange(OldSelection)
4420 -- Registers a history record for a change in the selection
4421
4422 -- Avoid overwriting history for selection actions
4423 if History.Index ~= #History.Stack then
4424 return;
4425 end;
4426
4427 -- Add the history record
4428 History.Add({
4429
4430 Before = OldSelection;
4431 After = Selection.Items;
4432
4433 Unapply = function (HistoryRecord)
4434 -- Reverts this change
4435
4436 -- Restore the old selection
4437 Selection.Replace(HistoryRecord.Before);
4438
4439 end;
4440
4441 Apply = function (HistoryRecord)
4442 -- Reapplies this change
4443
4444 -- Restore the new selection
4445 Selection.Replace(HistoryRecord.After);
4446
4447 end;
4448 });
4449
4450end;
4451
4452return Selection;
4453Serialization = {};
4454
4455-- Import services
4456Support = require(script.Parent.SupportLibrary);
4457Support.ImportServices();
4458
4459local Types = {
4460 Part = 0,
4461 WedgePart = 1,
4462 CornerWedgePart = 2,
4463 VehicleSeat = 3,
4464 Seat = 4,
4465 TrussPart = 5,
4466 SpecialMesh = 6,
4467 Texture = 7,
4468 Decal = 8,
4469 PointLight = 9,
4470 SpotLight = 10,
4471 SurfaceLight = 11,
4472 Smoke = 12,
4473 Fire = 13,
4474 Sparkles = 14,
4475 Model = 15
4476};
4477
4478local DefaultNames = {
4479 Part = 'Part',
4480 WedgePart = 'Wedge',
4481 CornerWedgePart = 'CornerWedge',
4482 VehicleSeat = 'VehicleSeat',
4483 Seat = 'Seat',
4484 TrussPart = 'Truss',
4485 SpecialMesh = 'Mesh',
4486 Texture = 'Texture',
4487 Decal = 'Decal',
4488 PointLight = 'PointLight',
4489 SpotLight = 'SpotLight',
4490 SurfaceLight = 'SurfaceLight',
4491 Smoke = 'Smoke',
4492 Fire = 'Fire',
4493 Sparkles = 'Sparkles',
4494 Model = 'Model'
4495};
4496
4497function Serialization.SerializeModel(Items)
4498 -- Returns a serialized version of the given model
4499
4500 -- Filter out non-serializable items in `Items`
4501 local SerializableItems = {};
4502 for Index, Item in ipairs(Items) do
4503 table.insert(SerializableItems, Types[Item.ClassName] and Item or nil);
4504 end;
4505 Items = SerializableItems;
4506
4507 -- Get a snapshot of the content
4508 local Keys = Support.FlipTable(Items);
4509
4510 local Data = {};
4511 Data.Version = 3;
4512 Data.Items = {};
4513
4514 -- Serialize each item in the model
4515 for Index, Item in pairs(Items) do
4516
4517 if Item:IsA 'BasePart' then
4518 local Datum = {};
4519 Datum[1] = Types[Item.ClassName];
4520 Datum[2] = Keys[Item.Parent] or 0;
4521 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4522 Datum[4] = Item.Size.X;
4523 Datum[5] = Item.Size.Y;
4524 Datum[6] = Item.Size.Z;
4525 Support.ConcatTable(Datum, { Item.CFrame:components() });
4526 Datum[19] = Item.Color.r;
4527 Datum[20] = Item.Color.g;
4528 Datum[21] = Item.Color.b;
4529 Datum[22] = Item.Material.Value;
4530 Datum[23] = Item.Anchored and 1 or 0;
4531 Datum[24] = Item.CanCollide and 1 or 0;
4532 Datum[25] = Item.Reflectance;
4533 Datum[26] = Item.Transparency;
4534 Datum[27] = Item.TopSurface.Value;
4535 Datum[28] = Item.BottomSurface.Value;
4536 Datum[29] = Item.FrontSurface.Value;
4537 Datum[30] = Item.BackSurface.Value;
4538 Datum[31] = Item.LeftSurface.Value;
4539 Datum[32] = Item.RightSurface.Value;
4540 Data.Items[Index] = Datum;
4541 end;
4542
4543 if Item.ClassName == 'Part' then
4544 local Datum = Data.Items[Index];
4545 Datum[33] = Item.Shape.Value;
4546 end;
4547
4548 if Item.ClassName == 'VehicleSeat' then
4549 local Datum = Data.Items[Index];
4550 Datum[33] = Item.MaxSpeed;
4551 Datum[34] = Item.Torque;
4552 Datum[35] = Item.TurnSpeed;
4553 end;
4554
4555 if Item.ClassName == 'TrussPart' then
4556 local Datum = Data.Items[Index];
4557 Datum[33] = Item.Style.Value;
4558 end;
4559
4560 if Item.ClassName == 'SpecialMesh' then
4561 local Datum = {};
4562 Datum[1] = Types[Item.ClassName];
4563 Datum[2] = Keys[Item.Parent] or 0;
4564 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4565 Datum[4] = Item.MeshType.Value;
4566 Datum[5] = Item.MeshId;
4567 Datum[6] = Item.TextureId;
4568 Datum[7] = Item.Offset.X;
4569 Datum[8] = Item.Offset.Y;
4570 Datum[9] = Item.Offset.Z;
4571 Datum[10] = Item.Scale.X;
4572 Datum[11] = Item.Scale.Y;
4573 Datum[12] = Item.Scale.Z;
4574 Datum[13] = Item.VertexColor.X;
4575 Datum[14] = Item.VertexColor.Y;
4576 Datum[15] = Item.VertexColor.Z;
4577 Data.Items[Index] = Datum;
4578 end;
4579
4580 if Item:IsA 'Decal' then
4581 local Datum = {};
4582 Datum[1] = Types[Item.ClassName];
4583 Datum[2] = Keys[Item.Parent] or 0;
4584 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4585 Datum[4] = Item.Texture;
4586 Datum[5] = Item.Transparency;
4587 Datum[6] = Item.Face.Value;
4588 Data.Items[Index] = Datum;
4589 end;
4590
4591 if Item.ClassName == 'Texture' then
4592 local Datum = Data.Items[Index];
4593 Datum[7] = Item.StudsPerTileU;
4594 Datum[8] = Item.StudsPerTileV;
4595 end;
4596
4597 if Item:IsA 'Light' then
4598 local Datum = {};
4599 Datum[1] = Types[Item.ClassName];
4600 Datum[2] = Keys[Item.Parent] or 0;
4601 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4602 Datum[4] = Item.Brightness;
4603 Datum[5] = Item.Color.r;
4604 Datum[6] = Item.Color.g;
4605 Datum[7] = Item.Color.b;
4606 Datum[8] = Item.Enabled and 1 or 0;
4607 Datum[9] = Item.Shadows and 1 or 0;
4608 Data.Items[Index] = Datum;
4609 end;
4610
4611 if Item.ClassName == 'PointLight' then
4612 local Datum = Data.Items[Index];
4613 Datum[10] = Item.Range;
4614 end;
4615
4616 if Item.ClassName == 'SpotLight' then
4617 local Datum = Data.Items[Index];
4618 Datum[10] = Item.Range;
4619 Datum[11] = Item.Angle;
4620 Datum[12] = Item.Face.Value;
4621 end;
4622
4623 if Item.ClassName == 'SurfaceLight' then
4624 local Datum = Data.Items[Index];
4625 Datum[10] = Item.Range;
4626 Datum[11] = Item.Angle;
4627 Datum[12] = Item.Face.Value;
4628 end;
4629
4630 if Item.ClassName == 'Smoke' then
4631 local Datum = {};
4632 Datum[1] = Types[Item.ClassName];
4633 Datum[2] = Keys[Item.Parent] or 0;
4634 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4635 Datum[4] = Item.Enabled and 1 or 0;
4636 Datum[5] = Item.Color.r;
4637 Datum[6] = Item.Color.g;
4638 Datum[7] = Item.Color.b;
4639 Datum[8] = Item.Size;
4640 Datum[9] = Item.RiseVelocity;
4641 Datum[10] = Item.Opacity;
4642 Data.Items[Index] = Datum;
4643 end;
4644
4645 if Item.ClassName == 'Fire' then
4646 local Datum = {};
4647 Datum[1] = Types[Item.ClassName];
4648 Datum[2] = Keys[Item.Parent] or 0;
4649 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4650 Datum[4] = Item.Enabled and 1 or 0;
4651 Datum[5] = Item.Color.r;
4652 Datum[6] = Item.Color.g;
4653 Datum[7] = Item.Color.b;
4654 Datum[8] = Item.SecondaryColor.r;
4655 Datum[9] = Item.SecondaryColor.g;
4656 Datum[10] = Item.SecondaryColor.b;
4657 Datum[11] = Item.Heat;
4658 Datum[12] = Item.Size;
4659 Data.Items[Index] = Datum;
4660 end;
4661
4662 if Item.ClassName == 'Sparkles' then
4663 local Datum = {};
4664 Datum[1] = Types[Item.ClassName];
4665 Datum[2] = Keys[Item.Parent] or 0;
4666 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4667 Datum[4] = Item.Enabled and 1 or 0;
4668 Datum[5] = Item.SparkleColor.r;
4669 Datum[6] = Item.SparkleColor.g;
4670 Datum[7] = Item.SparkleColor.b;
4671 Data.Items[Index] = Datum;
4672 end;
4673
4674 if Item.ClassName == 'Model' then
4675 local Datum = {};
4676 Datum[1] = Types[Item.ClassName];
4677 Datum[2] = Keys[Item.Parent] or 0;
4678 Datum[3] = Item.Name == DefaultNames[Item.ClassName] and '' or Item.Name;
4679 Datum[4] = Item.PrimaryPart and Keys[Item.PrimaryPart] or 0;
4680 Data.Items[Index] = Datum;
4681 end;
4682
4683 -- Spread the workload over time to avoid locking up the CPU
4684 if Index % 100 == 0 then
4685 wait(0.01);
4686 end;
4687
4688 end;
4689
4690 -- Return the serialized data
4691 return HttpService:JSONEncode(Data);
4692
4693end;
4694
4695function Serialization.InflateBuildData(Data)
4696 -- Returns an inflated version of the given build data
4697
4698 local Build = {};
4699 local Instances = {};
4700
4701 -- Create each instance
4702 for Index, Datum in ipairs(Data.Items) do
4703
4704 -- Inflate BaseParts
4705 if Datum[1] == Types.Part
4706 or Datum[1] == Types.WedgePart
4707 or Datum[1] == Types.CornerWedgePart
4708 or Datum[1] == Types.VehicleSeat
4709 or Datum[1] == Types.Seat
4710 or Datum[1] == Types.TrussPart
4711 then
4712 local Item = Instance.new(Support.FindTableOccurrence(Types, Datum[1]));
4713 Item.Size = Vector3.new(unpack(Support.Slice(Datum, 4, 6)));
4714 Item.CFrame = CFrame.new(unpack(Support.Slice(Datum, 7, 18)));
4715 Item.Color = Color3.new(Datum[19], Datum[20], Datum[21]);
4716 Item.Material = Datum[22];
4717 Item.Anchored = Datum[23] == 1;
4718 Item.CanCollide = Datum[24] == 1;
4719 Item.Reflectance = Datum[25];
4720 Item.Transparency = Datum[26];
4721 Item.TopSurface = Datum[27];
4722 Item.BottomSurface = Datum[28];
4723 Item.FrontSurface = Datum[29];
4724 Item.BackSurface = Datum[30];
4725 Item.LeftSurface = Datum[31];
4726 Item.RightSurface = Datum[32];
4727
4728 -- Register the part
4729 Instances[Index] = Item;
4730 end;
4731
4732 -- Inflate specific Part properties
4733 if Datum[1] == Types.Part then
4734 local Item = Instances[Index];
4735 Item.Shape = Datum[33];
4736 end;
4737
4738 -- Inflate specific VehicleSeat properties
4739 if Datum[1] == Types.VehicleSeat then
4740 local Item = Instances[Index];
4741 Item.MaxSpeed = Datum[33];
4742 Item.Torque = Datum[34];
4743 Item.TurnSpeed = Datum[35];
4744 end;
4745
4746 -- Inflate specific TrussPart properties
4747 if Datum[1] == Types.TrussPart then
4748 local Item = Instances[Index];
4749 Item.Style = Datum[33];
4750 end;
4751
4752 -- Inflate SpecialMesh instances
4753 if Datum[1] == Types.SpecialMesh then
4754 local Item = Instance.new('SpecialMesh');
4755 Item.MeshType = Datum[4];
4756 Item.MeshId = Datum[5];
4757 Item.TextureId = Datum[6];
4758 Item.Offset = Vector3.new(unpack(Support.Slice(Datum, 7, 9)));
4759 Item.Scale = Vector3.new(unpack(Support.Slice(Datum, 10, 12)));
4760 Item.VertexColor = Vector3.new(unpack(Support.Slice(Datum, 13, 15)));
4761
4762 -- Register the mesh
4763 Instances[Index] = Item;
4764 end;
4765
4766 -- Inflate Decal instances
4767 if Datum[1] == Types.Decal or Datum[1] == Types.Texture then
4768 local Item = Instance.new(Support.FindTableOccurrence(Types, Datum[1]));
4769 Item.Texture = Datum[4];
4770 Item.Transparency = Datum[5];
4771 Item.Face = Datum[6];
4772
4773 -- Register the Decal
4774 Instances[Index] = Item;
4775 end;
4776
4777 -- Inflate specific Texture properties
4778 if Datum[1] == Types.Texture then
4779 local Item = Instances[Index];
4780 Item.StudsPerTileU = Datum[7];
4781 Item.StudsPerTileV = Datum[8];
4782 end;
4783
4784 -- Inflate Light instances
4785 if Datum[1] == Types.PointLight
4786 or Datum[1] == Types.SpotLight
4787 or Datum[1] == Types.SurfaceLight
4788 then
4789 local Item = Instance.new(Support.FindTableOccurrence(Types, Datum[1]));
4790 Item.Brightness = Datum[4];
4791 Item.Color = Color3.new(unpack(Support.Slice(Datum, 5, 7)));
4792 Item.Enabled = Datum[8] == 1;
4793 Item.Shadows = Datum[9] == 1;
4794
4795 -- Register the light
4796 Instances[Index] = Item;
4797 end;
4798
4799 -- Inflate specific PointLight properties
4800 if Datum[1] == Types.PointLight then
4801 local Item = Instances[Index];
4802 Item.Range = Datum[10];
4803 end;
4804
4805 -- Inflate specific SpotLight properties
4806 if Datum[1] == Types.SpotLight then
4807 local Item = Instances[Index];
4808 Item.Range = Datum[10];
4809 Item.Angle = Datum[11];
4810 Item.Face = Datum[12];
4811 end;
4812
4813 -- Inflate specific SurfaceLight properties
4814 if Datum[1] == Types.SurfaceLight then
4815 local Item = Instances[Index];
4816 Item.Range = Datum[10];
4817 Item.Angle = Datum[11];
4818 Item.Face = Datum[12];
4819 end;
4820
4821 -- Inflate Smoke instances
4822 if Datum[1] == Types.Smoke then
4823 local Item = Instance.new('Smoke');
4824 Item.Enabled = Datum[4] == 1;
4825 Item.Color = Color3.new(unpack(Support.Slice(Datum, 5, 7)));
4826 Item.Size = Datum[8];
4827 Item.RiseVelocity = Datum[9];
4828 Item.Opacity = Datum[10];
4829
4830 -- Register the smoke
4831 Instances[Index] = Item;
4832 end;
4833
4834 -- Inflate Fire instances
4835 if Datum[1] == Types.Fire then
4836 local Item = Instance.new('Fire');
4837 Item.Enabled = Datum[4] == 1;
4838 Item.Color = Color3.new(unpack(Support.Slice(Datum, 5, 7)));
4839 Item.SecondaryColor = Color3.new(unpack(Support.Slice(Datum, 8, 10)));
4840 Item.Heat = Datum[11];
4841 Item.Size = Datum[12];
4842
4843 -- Register the fire
4844 Instances[Index] = Item;
4845 end;
4846
4847 -- Inflate Sparkles instances
4848 if Datum[1] == Types.Sparkles then
4849 local Item = Instance.new('Sparkles');
4850 Item.Enabled = Datum[4] == 1;
4851 Item.SparkleColor = Color3.new(unpack(Support.Slice(Datum, 5, 7)));
4852
4853 -- Register the instance
4854 Instances[Index] = Item;
4855 end;
4856
4857 -- Inflate Model instances
4858 if Datum[1] == Types.Model then
4859 local Item = Instance.new('Model');
4860
4861 -- Register the model
4862 Instances[Index] = Item;
4863 end;
4864
4865 end;
4866
4867 -- Set object values on each instance
4868 for Index, Datum in pairs(Data.Items) do
4869
4870 -- Get the item's instance
4871 local Item = Instances[Index];
4872
4873 -- Set each item's parent and name
4874 if Item and Datum[1] <= 15 then
4875 Item.Name = (Datum[3] == '') and DefaultNames[Item.ClassName] or Datum[3];
4876 if Datum[2] == 0 then
4877 table.insert(Build, Item);
4878 else
4879 Item.Parent = Instances[Datum[2]];
4880 end;
4881 end;
4882
4883 -- Set model primary parts
4884 if Item and Datum[1] == 15 then
4885 Item.PrimaryPart = (Datum[4] ~= 0) and Instances[Datum[4]] or nil;
4886 end;
4887
4888 end;
4889
4890 -- Return the model
4891 return Build;
4892
4893end;
4894
4895-- Return the API
4896return Serialization;
4897-- Libraries
4898Core = require(script.Parent.Core);
4899Support = Core.Support;
4900
4901SnapTracking = {};
4902SnapTracking.Enabled = false;
4903SnapTracking.TrackCorners = true;
4904SnapTracking.TrackFaceCentroids = true;
4905SnapTracking.TrackEdgeMidpoints = true;
4906
4907function SnapTracking.StartTracking(Callback)
4908 -- Starts displaying the given target's snap point nearest to the mouse, calls back every time a new point is approached
4909
4910 -- Make sure tracking isn't already on
4911 if SnapTracking.Enabled then
4912 SnapTracking.StopTracking();
4913 end;
4914
4915 -- Indicate that tracking is now enabled
4916 SnapTracking.Enabled = true;
4917
4918 -- Disable selection
4919 Core.Targeting.CancelSelecting();
4920
4921 -- Start the UI
4922 SnapTracking.StartUI();
4923
4924 -- Store callback to send changes in current snapping point
4925 SnapTracking.SetCallback(Callback);
4926
4927 -- Start tracking mouse movement
4928 function UpdateTrackingTarget(Input)
4929
4930 -- Blacklist the player's character and the items in `TargetBlacklist`
4931 local TargetBlacklist = Support.ConcatTable(
4932 { Player and Player.Character },
4933 SnapTracking.TargetBlacklist or {}
4934 );
4935
4936 -- Find the current target part and point
4937 local TargetRay = Workspace.CurrentCamera:ScreenPointToRay(Input.Position.X, Input.Position.Y);
4938 local TargetPart, TargetPoint, TargetNormal, TargetMaterial = Workspace:FindPartOnRayWithIgnoreList(
4939 Ray.new(TargetRay.Origin, TargetRay.Direction * 5000),
4940 TargetBlacklist
4941 );
4942
4943 -- Make sure a target part exists
4944 if not TargetPart then
4945 return;
4946 end;
4947
4948 -- Check with any snapping target filter
4949 if SnapTracking.TargetFilter and not SnapTracking.TargetFilter(TargetPart) then
4950 return;
4951 end;
4952
4953 -- Set the current target for snap point tracking
4954 SnapTracking.MousePoint = TargetPoint;
4955 SnapTracking.SetTrackingTarget(TargetPart);
4956
4957 end;
4958
4959 -- Update the tracking and UI to the current mouse and proximity state
4960 if not SnapTracking.CustomMouseTracking then
4961 SnapTracking.MouseTracking = Support.AddUserInputListener('Changed', 'MouseMovement', false, UpdateTrackingTarget);
4962 UpdateTrackingTarget({ Position = Vector2.new(Core.Mouse.X, Core.Mouse.Y) });
4963 SnapTracking.Update();
4964 end;
4965
4966end;
4967
4968function SnapTracking.SetCallback(Callback)
4969 -- Sets the function that is called back whenever a new snap point is in focus
4970 SnapTracking.Callback = Callback;
4971end;
4972
4973function SnapTracking.StartUI()
4974 -- Creates the point marking UI
4975 SnapTracking.PointMarker = Core.Tool.Interfaces.PointMarker:Clone();
4976 SnapTracking.PointMarker.Parent = Core.UI;
4977end;
4978
4979function SnapTracking.ClearUI()
4980 -- Removes the point marking UI
4981
4982 -- Make sure tracking is currently enabled
4983 if not SnapTracking.Enabled then
4984 return;
4985 end;
4986
4987 -- Remove the point marker UI
4988 SnapTracking.PointMarker:Destroy();
4989 SnapTracking.PointMarker = nil;
4990
4991end;
4992
4993function SnapTracking.Update()
4994 -- Updates the current closest point, reflects it on UI, calls callback function
4995
4996 -- Make sure tracking is currently enabled
4997 if not SnapTracking.Enabled then
4998 return;
4999 end;
5000
5001 -- Calculate the closest point
5002 local ClosestPoint = SnapTracking.GetClosestPoint();
5003
5004 -- Inform the callback function
5005 SnapTracking.Callback(ClosestPoint);
5006
5007 -- Update the point marker UI
5008 SnapTracking.UpdateUI(ClosestPoint);
5009
5010end;
5011
5012function SnapTracking.UpdateUI(Point)
5013 -- Updates the point marker UI to reflect the position of the current closest snap point
5014
5015 -- Make sure tracking is enabled, and that the UI has started
5016 if not SnapTracking.Enabled or not SnapTracking.PointMarker then
5017 return;
5018 end;
5019
5020 -- Make sure there's actually a point that needs to be marked, or hide the point marker
5021 if not Point then
5022 SnapTracking.PointMarker.Visible = false;
5023 return;
5024 end;
5025
5026 -- Map the point's position on the screen
5027 local PointPosition, PointVisible = Workspace.CurrentCamera:WorldToScreenPoint(Point.p);
5028
5029 -- Move the point marker UI to the point's position on the screen
5030 SnapTracking.PointMarker.Visible = PointVisible;
5031 SnapTracking.PointMarker.Position = UDim2.new(0, PointPosition.X, 0, PointPosition.Y);
5032
5033end;
5034
5035function SnapTracking.SetTrackingTarget(NewTarget)
5036 -- Sets the target part whose snapping points' proximity we are tracking
5037 SnapTracking.Target = NewTarget;
5038 SnapTracking.Update();
5039end;
5040
5041function SnapTracking.GetClosestPoint()
5042 -- Find the current nearest snapping point for the target, update the GUI
5043
5044 -- Make sure there's a target part to track, and a current mouse position to calculate proximity relative to
5045 if not SnapTracking.Target or not SnapTracking.MousePoint then
5046 return nil;
5047 end;
5048
5049 local SnappingPoints = {};
5050 local SnappingPointProximity = {};
5051
5052 -- Get the current target's snapping points
5053 local PartCFrame = SnapTracking.Target.CFrame;
5054 local PartSize = SnapTracking.Target.Size / 2;
5055 local SizeX, SizeY, SizeZ = PartSize.X, PartSize.Y, PartSize.Z;
5056
5057 -- Filter based on snapping point options
5058 if SnapTracking.TrackCorners then
5059 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, SizeY, SizeZ));
5060 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, SizeY, SizeZ));
5061 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, -SizeY, SizeZ));
5062 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, SizeY, -SizeZ));
5063 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, SizeY, -SizeZ));
5064 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, -SizeY, SizeZ));
5065 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, -SizeY, -SizeZ));
5066 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, -SizeY, -SizeZ));
5067 end;
5068 if SnapTracking.TrackEdgeMidpoints then
5069 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, SizeY, 0));
5070 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, 0, SizeZ));
5071 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, SizeY, SizeZ));
5072 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, SizeY, 0));
5073 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, 0, SizeZ));
5074 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, -SizeY, SizeZ));
5075 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, -SizeY, 0));
5076 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, 0, -SizeZ));
5077 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, SizeY, -SizeZ));
5078 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, -SizeY, 0));
5079 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, 0, -SizeZ));
5080 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, -SizeY, -SizeZ));
5081 end;
5082 if SnapTracking.TrackFaceCentroids then
5083 table.insert(SnappingPoints, PartCFrame * CFrame.new(SizeX, 0, 0));
5084 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, 0, SizeZ));
5085 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, SizeY, 0));
5086 table.insert(SnappingPoints, PartCFrame * CFrame.new(-SizeX, 0, 0));
5087 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, -SizeY, 0));
5088 table.insert(SnappingPoints, PartCFrame * CFrame.new(0, 0, -SizeZ));
5089 end;
5090
5091 -- Calculate proximity of each snapping point to the mouse
5092 for SnappingPointKey, SnappingPoint in ipairs(SnappingPoints) do
5093 SnappingPointProximity[SnappingPointKey] = (SnapTracking.MousePoint - SnappingPoint.p).magnitude;
5094 end;
5095
5096 -- Sort out the closest snapping point
5097 local ClosestPointKey = 1;
5098 for PointKey, Proximity in pairs(SnappingPointProximity) do
5099 if Proximity < SnappingPointProximity[ClosestPointKey] then
5100 ClosestPointKey = PointKey;
5101 end;
5102 end;
5103
5104 -- Return the closest point
5105 return SnappingPoints[ClosestPointKey];
5106end;
5107
5108function SnapTracking.StopTracking()
5109 -- Stops tracking the current closest snapping point, cleans up
5110
5111 -- Clear the previous tracking target, and callback
5112 SnapTracking.Target = nil;
5113 SnapTracking.Callback = nil;
5114
5115 -- Reset snapping point options
5116 SnapTracking.TrackFaceCentroids = true;
5117 SnapTracking.TrackEdgeMidpoints = true;
5118 SnapTracking.TrackCorners = true;
5119 SnapTracking.TargetFilter = nil;
5120 SnapTracking.TargetBlacklist = {};
5121
5122 -- Make sure we're currently tracking
5123 if not SnapTracking.Enabled then
5124 return;
5125 end;
5126
5127 -- Stop tracking the mouse and its proximity to snapping points
5128 SnapTracking.MouseTracking:disconnect();
5129 SnapTracking.MouseTracking = nil;
5130
5131 -- Clear the point marker UI from the screen
5132 SnapTracking.ClearUI();
5133
5134 -- Indicate that tracking is no longer enabled
5135 SnapTracking.Enabled = false;
5136
5137end;
5138
5139return SnapTracking;
5140SupportLibrary = {};
5141
5142function SupportLibrary.FindTableOccurrences(Haystack, Needle)
5143 -- Returns the positions of instances of `needle` in table `haystack`
5144
5145 local Positions = {};
5146
5147 -- Add any indexes from `Haystack` that are `Needle`
5148 for Index, Value in pairs(Haystack) do
5149 if Value == Needle then
5150 table.insert(Positions, Index);
5151 end;
5152 end;
5153
5154 return Positions;
5155end;
5156
5157function SupportLibrary.FindTableOccurrence(Haystack, Needle)
5158 -- Returns one occurrence of `Needle` in `Haystack`
5159
5160 -- Search for the first instance of `Needle` found and return it
5161 for Index, Value in pairs(Haystack) do
5162 if Value == Needle then
5163 return Index;
5164 end;
5165 end;
5166
5167 -- If no occurrences exist, return `nil`
5168 return nil;
5169
5170end;
5171
5172function SupportLibrary.IsInTable(Haystack, Needle)
5173 -- Returns whether the given `Needle` can be found within table `Haystack`
5174
5175 -- Go through every value in `Haystack` and return whether `Needle` is found
5176 for _, Value in pairs(Haystack) do
5177 if Value == Needle then
5178 return true;
5179 end;
5180 end;
5181
5182 -- If no instances were found, return false
5183 return false;
5184end;
5185
5186function SupportLibrary.DoTablesMatch(A, B)
5187 -- Returns whether the values of tables A and B are the same
5188
5189 -- Check B table differences
5190 for Index in pairs(A) do
5191 if A[Index] ~= B[Index] then
5192 return false;
5193 end;
5194 end;
5195
5196 -- Check A table differences
5197 for Index in pairs(B) do
5198 if B[Index] ~= A[Index] then
5199 return false;
5200 end;
5201 end;
5202
5203 -- Return true if no differences
5204 return true;
5205end;
5206
5207function SupportLibrary.Round(Number, Places)
5208 -- Returns `Number` rounded to the given number of decimal places (from lua-users)
5209
5210 -- Ensure that `Number` is a number
5211 if type(Number) ~= 'number' then
5212 return;
5213 end;
5214
5215 -- Round the number
5216 local Multiplier = 10 ^ (Places or 0);
5217 local RoundedNumber = math.floor(Number * Multiplier + 0.5) / Multiplier;
5218
5219 -- Return the rounded number
5220 return RoundedNumber;
5221end;
5222
5223function SupportLibrary.CloneTable(Table)
5224 -- Returns a copy of `Table`
5225
5226 local ClonedTable = {};
5227
5228 -- Copy all values into `ClonedTable`
5229 for Key, Value in pairs(Table) do
5230 ClonedTable[Key] = Value;
5231 end;
5232
5233 -- Return the clone
5234 return ClonedTable;
5235end;
5236
5237function SupportLibrary.GetAllDescendants(Parent)
5238 -- Recursively gets all the descendants of `Parent` and returns them
5239
5240 local Descendants = {};
5241
5242 for _, Child in pairs(Parent:GetChildren()) do
5243
5244 -- Add the direct descendants of `Parent`
5245 table.insert(Descendants, Child);
5246
5247 -- Add the descendants of each child
5248 for _, Subchild in pairs(SupportLibrary.GetAllDescendants(Child)) do
5249 table.insert(Descendants, Subchild);
5250 end;
5251
5252 end;
5253
5254 return Descendants;
5255end;
5256
5257function SupportLibrary.GetDescendantCount(Parent)
5258 -- Recursively gets a count of all the descendants of `Parent` and returns them
5259
5260 local Count = 0;
5261
5262 for _, Child in pairs(Parent:GetChildren()) do
5263
5264 -- Count the direct descendants of `Parent`
5265 Count = Count + 1;
5266
5267 -- Count and add the descendants of each child
5268 Count = Count + SupportLibrary.GetDescendantCount(Child);
5269
5270 end;
5271
5272 return Count;
5273end;
5274
5275function SupportLibrary.CloneParts(Parts)
5276 -- Returns a table of cloned `Parts`
5277
5278 local Clones = {};
5279
5280 -- Copy the parts into `Clones`
5281 for Index, Part in pairs(Parts) do
5282 Clones[Index] = Part:Clone();
5283 end;
5284
5285 return Clones;
5286end;
5287
5288function SupportLibrary.SplitString(String, Delimiter)
5289 -- Returns a table of string `String` split by pattern `Delimiter`
5290
5291 local StringParts = {};
5292 local Pattern = ('([^%s]+)'):format(Delimiter);
5293
5294 -- Capture each separated part
5295 String:gsub(Pattern, function (Part)
5296 table.insert(StringParts, Part);
5297 end);
5298
5299 return StringParts;
5300end;
5301
5302function SupportLibrary.GetChildOfClass(Parent, ClassName, Inherit)
5303 -- Returns the first child of `Parent` that is of class `ClassName`
5304 -- or nil if it couldn't find any
5305
5306 -- Look for a child of `Parent` of class `ClassName` and return it
5307 if not Inherit then
5308 for _, Child in pairs(Parent:GetChildren()) do
5309 if Child.ClassName == ClassName then
5310 return Child;
5311 end;
5312 end;
5313 else
5314 for _, Child in pairs(Parent:GetChildren()) do
5315 if Child:IsA(ClassName) then
5316 return Child;
5317 end;
5318 end;
5319 end;
5320
5321 return nil;
5322end;
5323
5324function SupportLibrary.GetChildrenOfClass(Parent, ClassName, Inherit)
5325 -- Returns a table containing the children of `Parent` that are
5326 -- of class `ClassName`
5327
5328 local Matches = {};
5329
5330 if not Inherit then
5331 for _, Child in pairs(Parent:GetChildren()) do
5332 if Child.ClassName == ClassName then
5333 table.insert(Matches, Child);
5334 end;
5335 end;
5336 else
5337 for _, Child in pairs(Parent:GetChildren()) do
5338 if Child:IsA(ClassName) then
5339 table.insert(Matches, Child);
5340 end;
5341 end;
5342 end;
5343
5344 return Matches;
5345end;
5346
5347function SupportLibrary.HSVToRGB(Hue, Saturation, Value)
5348 -- Returns the RGB equivalent of the given HSV-defined color
5349 -- (adapted from some code found around the web)
5350
5351 -- If it's achromatic, just return the value
5352 if Saturation == 0 then
5353 return Value;
5354 end;
5355
5356 -- Get the hue sector
5357 local HueSector = math.floor(Hue / 60);
5358 local HueSectorOffset = (Hue / 60) - HueSector;
5359
5360 local P = Value * (1 - Saturation);
5361 local Q = Value * (1 - Saturation * HueSectorOffset);
5362 local T = Value * (1 - Saturation * (1 - HueSectorOffset));
5363
5364 if HueSector == 0 then
5365 return Value, T, P;
5366 elseif HueSector == 1 then
5367 return Q, Value, P;
5368 elseif HueSector == 2 then
5369 return P, Value, T;
5370 elseif HueSector == 3 then
5371 return P, Q, Value;
5372 elseif HueSector == 4 then
5373 return T, P, Value;
5374 elseif HueSector == 5 then
5375 return Value, P, Q;
5376 end;
5377end;
5378
5379function SupportLibrary.RGBToHSV(Red, Green, Blue)
5380 -- Returns the HSV equivalent of the given RGB-defined color
5381 -- (adapted from some code found around the web)
5382
5383 local Hue, Saturation, Value;
5384
5385 local MinValue = math.min(Red, Green, Blue);
5386 local MaxValue = math.max(Red, Green, Blue);
5387
5388 Value = MaxValue;
5389
5390 local ValueDelta = MaxValue - MinValue;
5391
5392 -- If the color is not black
5393 if MaxValue ~= 0 then
5394 Saturation = ValueDelta / MaxValue;
5395
5396 -- If the color is purely black
5397 else
5398 Saturation = 0;
5399 Hue = -1;
5400 return Hue, Saturation, Value;
5401 end;
5402
5403 if Red == MaxValue then
5404 Hue = (Green - Blue) / ValueDelta;
5405 elseif Green == MaxValue then
5406 Hue = 2 + (Blue - Red) / ValueDelta;
5407 else
5408 Hue = 4 + (Red - Green) / ValueDelta;
5409 end;
5410
5411 Hue = Hue * 60;
5412 if Hue < 0 then
5413 Hue = Hue + 360;
5414 end;
5415
5416 return Hue, Saturation, Value;
5417end;
5418
5419function SupportLibrary.IdentifyCommonItem(Items)
5420 -- Returns the common item in table `Items`, or `nil` if
5421 -- they vary
5422
5423 local CommonItem = nil;
5424
5425 for ItemIndex, Item in pairs(Items) do
5426
5427 -- Set the initial item to compare against
5428 if ItemIndex == 1 then
5429 CommonItem = Item;
5430
5431 -- Check if this item is the same as the rest
5432 else
5433 -- If it isn't the same, there is no common item, so just stop right here
5434 if Item ~= CommonItem then
5435 return nil;
5436 end;
5437 end;
5438
5439 end;
5440
5441 -- Return the common item
5442 return CommonItem;
5443end;
5444
5445function SupportLibrary.IdentifyCommonProperty(Items, Property)
5446 -- Returns the common `Property` value in the instances given in `Items`
5447
5448 local PropertyVariations = {};
5449
5450 -- Capture all the variations of the property value
5451 for _, Item in pairs(Items) do
5452 table.insert(PropertyVariations, Item[Property]);
5453 end;
5454
5455 -- Return the common property value
5456 return SupportLibrary.IdentifyCommonItem(PropertyVariations);
5457
5458end;
5459
5460function SupportLibrary.CreateSignal()
5461 -- Returns a ROBLOX-like signal for connections (RbxUtility's is buggy)
5462
5463 local Signal = {
5464 Connections = {};
5465
5466 -- Provide a function to connect an event handler
5467 Connect = function (Signal, Handler)
5468
5469 -- Register the handler
5470 table.insert(Signal.Connections, Handler);
5471
5472 -- Return a controller for this connection
5473 local ConnectionController = {
5474
5475 -- Include a reference to the connection's handler
5476 Handler = Handler;
5477
5478 -- Provide a way to disconnect this connection
5479 Disconnect = function (Connection)
5480 local ConnectionSearch = SupportLibrary.FindTableOccurrences(Signal.Connections, Connection.Handler);
5481 if #ConnectionSearch > 0 then
5482 local ConnectionIndex = ConnectionSearch[1];
5483 table.remove(Signal.Connections, ConnectionIndex);
5484 end;
5485 end;
5486
5487 };
5488
5489 -- Add compatibility aliases
5490 ConnectionController.disconnect = ConnectionController.Disconnect;
5491
5492 -- Return the connection's controller
5493 return ConnectionController;
5494
5495 end;
5496
5497 -- Provide a function to trigger any connections' handlers
5498 Fire = function (Signal, ...)
5499 for _, Connection in pairs(Signal.Connections) do
5500 Connection(...);
5501 end;
5502 end;
5503 };
5504
5505 -- Add compatibility aliases
5506 Signal.connect = Signal.Connect;
5507 Signal.fire = Signal.Fire;
5508
5509 return Signal;
5510end;
5511
5512function SupportLibrary.GetPartCorners(Part)
5513 -- Returns a table of the given part's corners' CFrames
5514
5515 -- Make references to functions called a lot for efficiency
5516 local Insert = table.insert;
5517 local ToWorldSpace = CFrame.new().toWorldSpace;
5518 local NewCFrame = CFrame.new;
5519
5520 -- Get info about the part
5521 local PartCFrame = Part.CFrame;
5522 local SizeX, SizeY, SizeZ = Part.Size.x / 2, Part.Size.y / 2, Part.Size.z / 2;
5523
5524 -- Get each corner
5525 local Corners = {};
5526 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(SizeX, SizeY, SizeZ)));
5527 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(-SizeX, SizeY, SizeZ)));
5528 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(SizeX, -SizeY, SizeZ)));
5529 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(SizeX, SizeY, -SizeZ)));
5530 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(-SizeX, SizeY, -SizeZ)));
5531 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(-SizeX, -SizeY, SizeZ)));
5532 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(SizeX, -SizeY, -SizeZ)));
5533 Insert(Corners, ToWorldSpace(PartCFrame, NewCFrame(-SizeX, -SizeY, -SizeZ)));
5534
5535 return Corners;
5536end;
5537
5538function SupportLibrary.CreatePart(PartType)
5539 -- Creates and returns new part based on `PartType` with sensible defaults
5540
5541 local NewPart;
5542
5543 if PartType == 'Normal' then
5544 NewPart = Instance.new('Part');
5545 NewPart.Size = Vector3.new(4, 1, 2);
5546
5547 elseif PartType == 'Truss' then
5548 NewPart = Instance.new('TrussPart');
5549
5550 elseif PartType == 'Wedge' then
5551 NewPart = Instance.new('WedgePart');
5552 NewPart.Size = Vector3.new(4, 1, 2);
5553
5554 elseif PartType == 'Corner' then
5555 NewPart = Instance.new('CornerWedgePart');
5556
5557 elseif PartType == 'Cylinder' then
5558 NewPart = Instance.new('Part');
5559 NewPart.Shape = 'Cylinder';
5560 NewPart.TopSurface = Enum.SurfaceType.Smooth;
5561 NewPart.BottomSurface = Enum.SurfaceType.Smooth;
5562 NewPart.Size = Vector3.new(2, 2, 2);
5563
5564 elseif PartType == 'Ball' then
5565 NewPart = Instance.new('Part');
5566 NewPart.Shape = 'Ball';
5567 NewPart.TopSurface = Enum.SurfaceType.Smooth;
5568 NewPart.BottomSurface = Enum.SurfaceType.Smooth;
5569
5570 elseif PartType == 'Seat' then
5571 NewPart = Instance.new('Seat');
5572 NewPart.Size = Vector3.new(4, 1, 2);
5573
5574 elseif PartType == 'Vehicle Seat' then
5575 NewPart = Instance.new('VehicleSeat');
5576 NewPart.Size = Vector3.new(4, 1, 2);
5577
5578 elseif PartType == 'Spawn' then
5579 NewPart = Instance.new('SpawnLocation');
5580 NewPart.Size = Vector3.new(4, 1, 2);
5581 end;
5582
5583 -- Make sure the part is anchored
5584 NewPart.Anchored = true;
5585
5586 return NewPart;
5587end;
5588
5589function SupportLibrary.ImportServices()
5590 -- Adds references to common services into the calling environment
5591
5592 -- Get the calling environment
5593 local CallingEnvironment = getfenv(2);
5594
5595 -- Add the services
5596 CallingEnvironment.Workspace = Game:GetService 'Workspace';
5597 CallingEnvironment.Players = Game:GetService 'Players';
5598 CallingEnvironment.MarketplaceService = Game:GetService 'MarketplaceService';
5599 CallingEnvironment.ContentProvider = Game:GetService 'ContentProvider';
5600 CallingEnvironment.SoundService = Game:GetService 'SoundService';
5601 CallingEnvironment.UserInputService = Game:GetService 'UserInputService';
5602 CallingEnvironment.SelectionService = Game:GetService 'Selection';
5603 CallingEnvironment.CoreGui = Game:GetService 'CoreGui';
5604 CallingEnvironment.HttpService = Game:GetService 'HttpService';
5605 CallingEnvironment.ChangeHistoryService = Game:GetService 'ChangeHistoryService';
5606 CallingEnvironment.ReplicatedStorage = Game:GetService 'ReplicatedStorage';
5607 CallingEnvironment.GroupService = Game:GetService 'GroupService';
5608 CallingEnvironment.ServerScriptService = Game:GetService 'ServerScriptService';
5609 CallingEnvironment.ServerStorage = Game:GetService 'ServerStorage';
5610 CallingEnvironment.StarterGui = Game:GetService 'StarterGui';
5611 CallingEnvironment.RunService = Game:GetService 'RunService';
5612end;
5613
5614function SupportLibrary.GetListMembers(List, MemberName)
5615 -- Gets the given member for each object in the given list table
5616
5617 local Members = {};
5618
5619 -- Collect the member values for each item in the list
5620 for _, Item in pairs(List) do
5621 table.insert(Members, Item[MemberName]);
5622 end;
5623
5624 -- Return the members
5625 return Members;
5626
5627end;
5628
5629function SupportLibrary.AddUserInputListener(InputState, InputType, CatchAll, Callback)
5630 -- Connects to the given user input event and takes care of standard boilerplate code
5631
5632 -- Turn the given `InputType` string into a proper enum
5633 local InputType = Enum.UserInputType[InputType];
5634
5635 -- Create a UserInputService listener based on the given `InputState`
5636 return Game:GetService('UserInputService')['Input' .. InputState]:connect(function (Input, GameProcessedEvent)
5637
5638 -- Make sure this input was not captured by the client (unless `CatchAll` is enabled)
5639 if GameProcessedEvent and not CatchAll then
5640 return;
5641 end;
5642
5643 -- Make sure this is the right input type
5644 if Input.UserInputType ~= InputType then
5645 return;
5646 end;
5647
5648 -- Make sure any key input did not occur while typing into a UI
5649 if InputType == Enum.UserInputType.Keyboard and Game:GetService('UserInputService'):GetFocusedTextBox() then
5650 return;
5651 end;
5652
5653 -- Call back upon passing all conditions
5654 Callback(Input);
5655
5656 end);
5657
5658end;
5659
5660function SupportLibrary.AddGuiInputListener(Gui, InputState, InputType, CatchAll, Callback)
5661 -- Connects to the given GUI user input event and takes care of standard boilerplate code
5662
5663 -- Turn the given `InputType` string into a proper enum
5664 local InputType = Enum.UserInputType[InputType];
5665
5666 -- Create a UserInputService listener based on the given `InputState`
5667 return Gui['Input' .. InputState]:connect(function (Input, GameProcessedEvent)
5668
5669 -- Make sure this input was not captured by the client (unless `CatchAll` is enabled)
5670 if GameProcessedEvent and not CatchAll then
5671 return;
5672 end;
5673
5674 -- Make sure this is the right input type
5675 if Input.UserInputType ~= InputType then
5676 return;
5677 end;
5678
5679 -- Call back upon passing all conditions
5680 Callback(Input);
5681
5682 end);
5683
5684end;
5685
5686function SupportLibrary.AreKeysPressed(...)
5687 -- Returns whether the given keys are pressed
5688
5689 local RequestedKeysPressed = 0;
5690
5691 -- Get currently pressed keys
5692 local PressedKeys = SupportLibrary.GetListMembers(Game:GetService('UserInputService'):GetKeysPressed(), 'KeyCode');
5693
5694 -- Go through each requested key
5695 for _, Key in pairs({ ... }) do
5696
5697 -- Count requested keys that are pressed
5698 if SupportLibrary.IsInTable(PressedKeys, Key) then
5699 RequestedKeysPressed = RequestedKeysPressed + 1;
5700 end;
5701
5702 end;
5703
5704 -- Return whether all the requested keys are pressed or not
5705 return RequestedKeysPressed == #{...};
5706
5707end;
5708
5709function SupportLibrary.ConcatTable(DestinationTable, SourceTable)
5710 -- Inserts all values of SourceTable into DestinationTable
5711
5712 -- Add each value from `SourceTable` into `DestinationTable`
5713 for _, Value in ipairs(SourceTable) do
5714 table.insert(DestinationTable, Value);
5715 end;
5716
5717 -- Return the destination table
5718 return DestinationTable;
5719end;
5720
5721function SupportLibrary.ClearTable(Table)
5722 -- Clears out every value in `Table`
5723
5724 -- Clear each index
5725 for Index in pairs(Table) do
5726 Table[Index] = nil;
5727 end;
5728
5729 -- Return the given table
5730 return Table;
5731end;
5732
5733function SupportLibrary.Values(Table)
5734 -- Returns all the values in the given table
5735
5736 local Values = {};
5737
5738 -- Go through each key and get each value
5739 for _, Value in pairs(Table) do
5740 table.insert(Values, Value);
5741 end;
5742
5743 -- Return the values
5744 return Values;
5745end;
5746
5747function SupportLibrary.Keys(Table)
5748 -- Returns all the keys in the given table
5749
5750 local Keys = {};
5751
5752 -- Go through each key and get each value
5753 for Key in pairs(Table) do
5754 table.insert(Keys, Key);
5755 end;
5756
5757 -- Return the values
5758 return Keys;
5759end;
5760
5761function SupportLibrary.Call(Function, ...)
5762 -- Returns a callback to `Function` with the given arguments
5763 local Args = { ... };
5764 return function (...)
5765 return Function(unpack(
5766 SupportLibrary.ConcatTable(SupportLibrary.CloneTable(Args), { ... })
5767 ));
5768 end;
5769end;
5770
5771function SupportLibrary.Trim(String)
5772 -- Returns a trimmed version of `String` (adapted from code from lua-users)
5773 return (String:gsub("^%s*(.-)%s*$", "%1"));
5774end
5775
5776function SupportLibrary.ChainCall(...)
5777 -- Returns function that passes arguments through given functions and returns the final result
5778
5779 -- Get the given chain of functions
5780 local Chain = { ... };
5781
5782 -- Return the chaining function
5783 return function (...)
5784
5785 -- Get arguments
5786 local Arguments = { ... };
5787
5788 -- Go through each function and store the returned data to reuse in the next function's arguments
5789 for _, Function in ipairs(Chain) do
5790 Arguments = { Function(unpack(Arguments)) };
5791 end;
5792
5793 -- Return the final returned data
5794 return unpack(Arguments);
5795
5796 end;
5797
5798end;
5799
5800function SupportLibrary.CountKeys(Table)
5801 -- Returns the number of keys in `Table`
5802
5803 local Count = 0;
5804
5805 -- Count each key
5806 for _ in pairs(Table) do
5807 Count = Count + 1;
5808 end;
5809
5810 -- Return the count
5811 return Count;
5812
5813end;
5814
5815function SupportLibrary.Slice(Table, Start, End)
5816 -- Returns values from `Start` to `End` in `Table`
5817
5818 local Slice = {};
5819
5820 -- Go through the given indices
5821 for Index = Start, End do
5822 table.insert(Slice, Table[Index]);
5823 end;
5824
5825 -- Return the slice
5826 return Slice;
5827
5828end;
5829
5830function SupportLibrary.FlipTable(Table)
5831 -- Returns a table with keys and values in `Table` swapped
5832
5833 local FlippedTable = {};
5834
5835 -- Flip each key and value
5836 for Key, Value in pairs(Table) do
5837 FlippedTable[Value] = Key;
5838 end;
5839
5840 -- Return the flipped table
5841 return FlippedTable;
5842
5843end;
5844
5845function SupportLibrary.ScheduleRecurringTask(TaskFunction, Interval)
5846 -- Repeats `Task` every `Interval` seconds until stopped
5847
5848 -- Create a task object
5849 local Task = {
5850
5851 -- A switch determining if it's running or not
5852 Running = true;
5853
5854 -- A function to stop this task
5855 Stop = function (Task)
5856 Task.Running = false;
5857 end;
5858
5859 -- References to the task function and set interval
5860 TaskFunction = TaskFunction;
5861 Interval = Interval;
5862
5863 };
5864
5865 coroutine.wrap(function (Task)
5866
5867 -- Repeat the task
5868 while wait(Task.Interval) and Task.Running do
5869 Task.TaskFunction();
5870 end;
5871
5872 end)(Task);
5873
5874 -- Return the task object
5875 return Task;
5876
5877end;
5878
5879function SupportLibrary.Clamp(Number, Minimum, Maximum)
5880 -- Returns the given number, clamped according to the provided min/max
5881
5882 -- Clamp the number
5883 if Minimum and Number < Minimum then
5884 Number = Minimum;
5885 elseif Maximum and Number > Maximum then
5886 Number = Maximum;
5887 end;
5888
5889 -- Return the clamped number
5890 return Number;
5891
5892end;
5893
5894function SupportLibrary.ReverseTable(Table)
5895 -- Returns a new table with values in the opposite order
5896
5897 local ReversedTable = {};
5898
5899 -- Copy each value at the opposite key
5900 for Index, Value in ipairs(Table) do
5901 ReversedTable[#Table - Index + 1] = Value;
5902 end;
5903
5904 -- Return the reversed table
5905 return ReversedTable;
5906
5907end;
5908
5909return SupportLibrary;
5910-- Libraries
5911RbxUtility = LoadLibrary 'RbxUtility';
5912Create = RbxUtility.Create;
5913Support = require(script.Parent.SupportLibrary);
5914Selection = require(script.Parent.SelectionModule);
5915
5916TargetingModule = {};
5917TargetingModule.TargetChanged = RbxUtility.CreateSignal();
5918
5919function TargetingModule.EnableTargeting()
5920 -- Begin targeting parts from the mouse
5921
5922 -- Get core API
5923 local Core = GetCore();
5924 local Connections = Core.Connections;
5925
5926 -- Create reference to mouse
5927 Mouse = Core.Mouse;
5928
5929 -- Listen for target changes
5930 Connections.Targeting = Mouse.Move:connect(TargetingModule.UpdateTarget);
5931
5932 -- Listen for target clicks
5933 Connections.Selecting = Mouse.Button1Up:connect(TargetingModule.SelectTarget);
5934
5935 -- Listen for sibling selection middle clicks
5936 Connections.SiblingSelecting = Support.AddUserInputListener('Began', 'MouseButton3', true, function ()
5937 TargetingModule.SelectSiblings(Mouse.Target, not Selection.Multiselecting);
5938 end);
5939
5940 -- Listen for 2D selection
5941 Connections.RectSelectionStarted = Mouse.Button1Down:connect(TargetingModule.StartRectangleSelecting);
5942 Connections.RectSelectionFinished = Support.AddUserInputListener('Ended', 'MouseButton1', true, TargetingModule.FinishRectangleSelecting);
5943
5944 -- Hide target box when tool is unequipped
5945 Connections.HideTargetBoxOnDisable = Core.Disabling:connect(TargetingModule.HighlightTarget);
5946
5947 -- Cancel any ongoing selection when tool is unequipped
5948 Connections.CancelSelectionOnDisable = Core.Disabling:connect(TargetingModule.CancelRectangleSelecting);
5949
5950end;
5951
5952function TargetingModule.UpdateTarget()
5953
5954 -- Ensure target has changed
5955 if Target == Mouse.Target then
5956 return;
5957 end;
5958
5959 -- Update target
5960 Target = Mouse.Target;
5961
5962 -- Fire events
5963 TargetingModule.TargetChanged:fire(Mouse.Target);
5964
5965end;
5966
5967function TargetingModule.HighlightTarget(Target)
5968
5969 -- Get core API
5970 local Core = GetCore();
5971
5972 -- Create target box
5973 if not TargetBox then
5974 TargetBox = Create 'SelectionBox' {
5975 Name = 'BTTargetOutline',
5976 Parent = Core.UIContainer,
5977 LineThickness = 0.025,
5978 Transparency = 0.5,
5979 Color = BrickColor.new 'Institutional white'
5980 };
5981 end;
5982
5983 -- Focus on target
5984 TargetBox.Parent = Target and Core.UIContainer or nil;
5985 TargetBox.Adornee = Target;
5986
5987end;
5988
5989function TargetingModule.SelectTarget()
5990
5991 -- Ensure target selection isn't cancelled
5992 if SelectionCancelled then
5993 SelectionCancelled = false;
5994 return;
5995 end;
5996
5997 -- Focus on clicked, selected item
5998 if not Selection.Multiselecting and Selection.IsSelected(Target) then
5999 Selection.SetFocus(Target);
6000 return;
6001 end;
6002
6003 -- Clear selection if invalid target selected
6004 if not GetCore().IsSelectable(Target) then
6005 Selection.Clear(true);
6006 return;
6007 end;
6008
6009 -- Unselect clicked, selected item if multiselection is enabled
6010 if Selection.Multiselecting and Selection.IsSelected(Target) then
6011 Selection.Remove({ Target }, true);
6012 return;
6013 end;
6014
6015 -- Add to selection if multiselecting
6016 if Selection.Multiselecting then
6017 Selection.Add({ Target }, true);
6018
6019 -- Replace selection if not multiselecting
6020 else
6021 Selection.Replace({ Target }, true);
6022 end;
6023
6024end;
6025
6026function TargetingModule.SelectSiblings(Part, ReplaceSelection)
6027 -- Selects all parts under the same parent as `Part`
6028
6029 -- If a part is not specified, assume the currently focused part
6030 local Part = Part or Selection.Focus;
6031
6032 -- Ensure the part exists and its parent is not Workspace
6033 if not Part or Part.Parent == Workspace then
6034 return;
6035 end;
6036
6037 -- Get the focused item's siblings
6038 local Siblings = Support.GetAllDescendants(Part.Parent);
6039
6040 -- Add to or replace selection
6041 if ReplaceSelection then
6042 Selection.Replace(Siblings, true);
6043 else
6044 Selection.Add(Siblings, true);
6045 end;
6046
6047end;
6048
6049function TargetingModule.StartRectangleSelecting()
6050
6051 -- Ensure selection isn't cancelled
6052 if SelectionCancelled then
6053 return;
6054 end;
6055
6056 -- Mark where rectangle selection started
6057 RectangleSelectStart = Vector2.new(Mouse.X, Mouse.Y);
6058
6059 -- Track mouse while rectangle selecting
6060 GetCore().Connections.WatchRectangleSelection = Mouse.Move:connect(function ()
6061
6062 -- If rectangle selecting, update rectangle
6063 if RectangleSelecting then
6064 TargetingModule.UpdateSelectionRectangle();
6065
6066 -- Watch for potential rectangle selections
6067 elseif RectangleSelectStart and (Vector2.new(Mouse.X, Mouse.Y) - RectangleSelectStart).magnitude >= 10 then
6068 RectangleSelecting = true;
6069 SelectionCancelled = true;
6070 end;
6071
6072 end);
6073
6074end;
6075
6076function TargetingModule.UpdateSelectionRectangle()
6077
6078 -- Ensure rectangle selection is ongoing
6079 if not RectangleSelecting then
6080 return;
6081 end;
6082
6083 -- Get core API
6084 local Core = GetCore();
6085
6086 -- Create selection rectangle
6087 if not SelectionRectangle then
6088 SelectionRectangle = Create 'Frame' {
6089 Name = 'SelectionRectangle',
6090 Parent = Core.UI,
6091 BackgroundColor3 = Color3.fromRGB(100, 100, 100),
6092 BorderColor3 = Color3.new(0, 0, 0),
6093 BackgroundTransparency = 0.5,
6094 BorderSizePixel = 1
6095 };
6096 end;
6097
6098 local StartPoint = Vector2.new(
6099 math.min(RectangleSelectStart.X, Mouse.X),
6100 math.min(RectangleSelectStart.Y, Mouse.Y)
6101 );
6102 local EndPoint = Vector2.new(
6103 math.max(RectangleSelectStart.X, Mouse.X),
6104 math.max(RectangleSelectStart.Y, Mouse.Y)
6105 );
6106
6107 -- Update size and position
6108 SelectionRectangle.Parent = Core.UI;
6109 SelectionRectangle.Position = UDim2.new(0, StartPoint.X, 0, StartPoint.Y);
6110 SelectionRectangle.Size = UDim2.new(0, EndPoint.X - StartPoint.X, 0, EndPoint.Y - StartPoint.Y);
6111
6112end;
6113
6114function TargetingModule.CancelRectangleSelecting()
6115
6116 -- Prevent potential rectangle selections
6117 RectangleSelectStart = nil;
6118
6119 -- Clear ongoing rectangle selection
6120 RectangleSelecting = false;
6121
6122 -- Clear rectangle selection watcher
6123 local Connections = GetCore().Connections;
6124 if Connections.WatchRectangleSelection then
6125 Connections.WatchRectangleSelection:disconnect();
6126 Connections.WatchRectangleSelection = nil;
6127 end;
6128
6129 -- Clear rectangle UI
6130 if SelectionRectangle then
6131 SelectionRectangle.Parent = nil;
6132 end;
6133
6134end;
6135
6136function TargetingModule.CancelSelecting()
6137 SelectionCancelled = true;
6138 TargetingModule.CancelRectangleSelecting();
6139end;
6140
6141function TargetingModule.FinishRectangleSelecting()
6142
6143 local RectangleSelecting = RectangleSelecting;
6144 local RectangleSelectStart = RectangleSelectStart;
6145
6146 -- Clear rectangle selection
6147 TargetingModule.CancelRectangleSelecting();
6148
6149 -- Ensure rectangle selection is ongoing
6150 if not RectangleSelecting then
6151 return;
6152 end;
6153
6154 -- Get rectangle dimensions
6155 local StartPoint = Vector2.new(
6156 math.min(RectangleSelectStart.X, Mouse.X),
6157 math.min(RectangleSelectStart.Y, Mouse.Y)
6158 );
6159 local EndPoint = Vector2.new(
6160 math.max(RectangleSelectStart.X, Mouse.X),
6161 math.max(RectangleSelectStart.Y, Mouse.Y)
6162 );
6163
6164 local SelectableItems = {};
6165
6166 -- Find items that lie within the rectangle
6167 for _, Part in pairs(Support.GetAllDescendants(Workspace)) do
6168 if Part:IsA 'BasePart' then
6169 local ScreenPoint, OnScreen = Workspace.CurrentCamera:WorldToScreenPoint(Part.Position);
6170 if OnScreen then
6171 local LeftCheck = ScreenPoint.X >= StartPoint.X;
6172 local RightCheck = ScreenPoint.X <= EndPoint.X;
6173 local TopCheck = ScreenPoint.Y >= StartPoint.Y;
6174 local BottomCheck = ScreenPoint.Y <= EndPoint.Y;
6175 table.insert(SelectableItems, (LeftCheck and RightCheck and TopCheck and BottomCheck) and Part or nil);
6176 end;
6177 end;
6178 end;
6179
6180 -- Add to selection if multiselecting
6181 if Selection.Multiselecting then
6182 Selection.Add(SelectableItems, true);
6183
6184 -- Replace selection if not multiselecting
6185 else
6186 Selection.Replace(SelectableItems, true);
6187 end;
6188
6189end;
6190
6191function TargetingModule.PrismSelect()
6192 -- Selects parts in the currently selected parts
6193
6194 -- Ensure parts are selected
6195 if #Selection.Items == 0 then
6196 return;
6197 end;
6198
6199 -- Get core API
6200 local Core = GetCore();
6201
6202 -- Get region for selection items and find potential parts
6203 local Extents = require(Core.Tool.BoundingBoxModule).CalculateExtents(Selection.Items, nil, true);
6204 local Region = Region3.new(Extents.Min, Extents.Max);
6205 local PotentialParts = Workspace:FindPartsInRegion3WithIgnoreList(Region, Selection.Items, math.huge);
6206
6207 -- Enable collision on all potential parts
6208 local OriginalState = {};
6209 for _, PotentialPart in pairs(PotentialParts) do
6210 OriginalState[PotentialPart] = { Anchored = PotentialPart.Anchored, CanCollide = PotentialPart.CanCollide };
6211 PotentialPart.Anchored = true;
6212 PotentialPart.CanCollide = true;
6213 end;
6214
6215 local Parts = {};
6216
6217 -- Find all parts intersecting with selection
6218 for _, Part in pairs(Selection.Items) do
6219 local TouchingParts = Part:GetTouchingParts();
6220 for _, TouchingPart in pairs(TouchingParts) do
6221 if not Selection.IsSelected(TouchingPart) then
6222 Parts[TouchingPart] = true;
6223 end;
6224 end;
6225 end;
6226
6227 -- Restore all potential parts' original states
6228 for PotentialPart, State in pairs(OriginalState) do
6229 PotentialPart.CanCollide = State.CanCollide;
6230 PotentialPart.Anchored = State.Anchored;
6231 end;
6232
6233 -- Delete the selection parts
6234 Core.DeleteSelection();
6235
6236 -- Select all found parts
6237 Selection.Replace(Support.Keys(Parts), true);
6238
6239end;
6240
6241TargetingModule.TargetChanged:connect(function (Target)
6242
6243 -- Get core API
6244 local Core = GetCore();
6245
6246 -- Hide target box if no/unselectable target
6247 if not Target or not Core.IsSelectable(Target) or Core.Selection.IsSelected(Target) then
6248 TargetingModule.HighlightTarget(nil);
6249
6250 -- Show target outline if target is selectable
6251 else
6252 TargetingModule.HighlightTarget(Target);
6253 end;
6254
6255end);
6256
6257function GetCore()
6258 return require(script.Parent.Core);
6259end;
6260
6261return TargetingModule;