Advertisement
Shrektella

Untitled

Jan 19th, 2017
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 74.27 KB | None | 0 0
  1. ------------------------------------------
  2. -- Create references to important objects
  3. ------------------------------------------
  4. Services = {
  5. ["Workspace"] = Game:GetService( "Workspace" );
  6. ["Players"] = Game:GetService( "Players" );
  7. ["Lighting"] = Game:GetService( "Lighting" );
  8. ["Teams"] = Game:GetService( "Teams" );
  9. ["Debris"] = Game:GetService( "Debris" );
  10. ["MarketplaceService"] = Game:GetService( "MarketplaceService" );
  11. ["JointsService"] = Game.JointsService;
  12. ["BadgeService"] = Game:GetService( "BadgeService" );
  13. ["RunService"] = Game:GetService( "RunService" );
  14. ["ContentProvider"] = Game:GetService( "ContentProvider" );
  15. ["TeleportService"] = Game:GetService( "TeleportService" );
  16. ["SoundService"] = Game:GetService( "SoundService" );
  17. ["InsertService"] = Game:GetService( "InsertService" );
  18. ["CollectionService"] = Game:GetService( "CollectionService" );
  19. ["UserInputService"] = Game:GetService( "UserInputService" );
  20. ["GamePassService"] = Game:GetService( "GamePassService" );
  21. ["StarterPack"] = Game:GetService( "StarterPack" );
  22. ["StarterGui"] = Game:GetService( "StarterGui" );
  23. ["TestService"] = Game:GetService( "TestService" );
  24. ["ReplicatedStorage"] = Game:GetService( "ReplicatedStorage" );
  25. ["Selection"] = Game:GetService( "Selection" );
  26. ["CoreGui"] = Game:GetService( "CoreGui" );
  27. };
  28.  
  29. Tool = script.Parent;
  30. Player = Services.Players.LocalPlayer;
  31. Mouse = nil;
  32.  
  33. -- Determine whether this is the plugin or tool
  34. if plugin then
  35. ToolType = 'plugin';
  36. elseif Tool:IsA( 'Tool' ) then
  37. ToolType = 'tool';
  38. end;
  39.  
  40. -- Get tool type-specific resources
  41. if ToolType == 'tool' then
  42. GUIContainer = Player:WaitForChild( 'PlayerGui' );
  43. in_server = not not Game:FindFirstChild( 'NetworkClient' );
  44. elseif ToolType == 'plugin' then
  45. GUIContainer = Services.CoreGui;
  46. in_server = not not Game:FindFirstChild( 'NetworkServer' );
  47. end;
  48. if in_server then
  49. Tool:WaitForChild( "GetAsync" );
  50. Tool:WaitForChild( "PostAsync" );
  51. GetAsync = function ( ... )
  52. return Tool.GetAsync:InvokeServer( ... );
  53. end;
  54. PostAsync = function ( ... )
  55. return Tool.PostAsync:InvokeServer( ... );
  56. end;
  57. end;
  58.  
  59. ToolAssetID = 142785488;
  60.  
  61. dark_slanted_rectangle = "http://www.roblox.com/asset/?id=127774197";
  62. light_slanted_rectangle = "http://www.roblox.com/asset/?id=127772502";
  63. action_completion_sound = "http://www.roblox.com/asset/?id=99666917";
  64. expand_arrow = "http://www.roblox.com/asset/?id=134367382";
  65. tool_decal = "http://www.roblox.com/asset/?id=129748355";
  66. undo_active_decal = "http://www.roblox.com/asset/?id=141741408";
  67. undo_inactive_decal = "http://www.roblox.com/asset/?id=142074557";
  68. redo_active_decal = "http://www.roblox.com/asset/?id=141741327";
  69. redo_inactive_decal = "http://www.roblox.com/asset/?id=142074553";
  70. delete_active_decal = "http://www.roblox.com/asset/?id=141896298";
  71. delete_inactive_decal = "http://www.roblox.com/asset/?id=142074644";
  72. export_active_decal = "http://www.roblox.com/asset/?id=141741337";
  73. export_inactive_decal = "http://www.roblox.com/asset/?id=142074569";
  74. clone_active_decal = "http://www.roblox.com/asset/?id=142073926";
  75. clone_inactive_decal = "http://www.roblox.com/asset/?id=142074563";
  76. plugin_icon = "http://www.roblox.com/asset/?id=142287521";
  77.  
  78. ------------------------------------------
  79. -- Load external dependencies
  80. ------------------------------------------
  81. RbxUtility = LoadLibrary( "RbxUtility" );
  82. Services.ContentProvider:Preload( dark_slanted_rectangle );
  83. Services.ContentProvider:Preload( light_slanted_rectangle );
  84. Services.ContentProvider:Preload( action_completion_sound );
  85. Services.ContentProvider:Preload( expand_arrow );
  86. Services.ContentProvider:Preload( tool_decal );
  87. Services.ContentProvider:Preload( undo_active_decal );
  88. Services.ContentProvider:Preload( undo_inactive_decal );
  89. Services.ContentProvider:Preload( redo_inactive_decal );
  90. Services.ContentProvider:Preload( redo_active_decal );
  91. Services.ContentProvider:Preload( delete_active_decal );
  92. Services.ContentProvider:Preload( delete_inactive_decal );
  93. Services.ContentProvider:Preload( export_active_decal );
  94. Services.ContentProvider:Preload( export_inactive_decal );
  95. Services.ContentProvider:Preload( clone_active_decal );
  96. Services.ContentProvider:Preload( clone_inactive_decal );
  97. Services.ContentProvider:Preload( plugin_icon );
  98. Tool:WaitForChild( "Interfaces" );
  99. repeat wait( 0 ) until _G.gloo;
  100. Gloo = _G.gloo;
  101.  
  102. ------------------------------------------
  103. -- Define functions that are depended-upon
  104. ------------------------------------------
  105. function _findTableOccurrences( haystack, needle )
  106. -- Returns the positions of instances of `needle` in table `haystack`
  107. local positions = {};
  108.  
  109. -- Add any indexes from `haystack` that have `needle`
  110. for index, value in pairs( haystack ) do
  111. if value == needle then
  112. table.insert( positions, index );
  113. end;
  114. end;
  115.  
  116. return positions;
  117. end;
  118.  
  119. function _getCollectionInfo( part_collection )
  120. -- Returns the size and position of collection of parts `part_collection`
  121.  
  122. -- Get the corners
  123. local corners = {};
  124.  
  125. -- Create shortcuts to certain things that are expensive to call constantly
  126. -- (note: otherwise it actually becomes an issue if the selection grows
  127. -- considerably large)
  128. local table_insert = table.insert;
  129. local newCFrame = CFrame.new;
  130.  
  131. for _, Part in pairs( part_collection ) do
  132.  
  133. local PartCFrame = Part.CFrame;
  134. local partCFrameOffset = PartCFrame.toWorldSpace;
  135. local PartSize = Part.Size / 2;
  136. local size_x, size_y, size_z = PartSize.x, PartSize.y, PartSize.z;
  137.  
  138. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( size_x, size_y, size_z ) ) );
  139. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( -size_x, size_y, size_z ) ) );
  140. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( size_x, -size_y, size_z ) ) );
  141. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( size_x, size_y, -size_z ) ) );
  142. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( -size_x, size_y, -size_z ) ) );
  143. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( -size_x, -size_y, size_z ) ) );
  144. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( size_x, -size_y, -size_z ) ) );
  145. table_insert( corners, partCFrameOffset( PartCFrame, newCFrame( -size_x, -size_y, -size_z ) ) );
  146.  
  147. end;
  148.  
  149. -- Get the extents
  150. local x, y, z = {}, {}, {};
  151.  
  152. for _, Corner in pairs( corners ) do
  153. table_insert( x, Corner.x );
  154. table_insert( y, Corner.y );
  155. table_insert( z, Corner.z );
  156. end;
  157.  
  158. local x_min, y_min, z_min = math.min( unpack( x ) ),
  159. math.min( unpack( y ) ),
  160. math.min( unpack( z ) );
  161.  
  162. local x_max, y_max, z_max = math.max( unpack( x ) ),
  163. math.max( unpack( y ) ),
  164. math.max( unpack( z ) );
  165.  
  166. -- Get the size between the extents
  167. local x_size, y_size, z_size = x_max - x_min,
  168. y_max - y_min,
  169. z_max - z_min;
  170.  
  171. local Size = Vector3.new( x_size, y_size, z_size );
  172.  
  173. -- Get the centroid of the collection of points
  174. local Position = CFrame.new( x_min + ( x_max - x_min ) / 2,
  175. y_min + ( y_max - y_min ) / 2,
  176. z_min + ( z_max - z_min ) / 2 );
  177.  
  178. -- Return the size of the collection of parts
  179. return Size, Position;
  180. end;
  181.  
  182. function _round( number, places )
  183. -- Returns `number` rounded to the number of decimal `places`
  184. -- (from lua-users)
  185.  
  186. local mult = 10 ^ ( places or 0 );
  187.  
  188. return math.floor( number * mult + 0.5 ) / mult;
  189.  
  190. end
  191.  
  192. function _cloneTable( source )
  193. -- Returns a deep copy of table `source`
  194.  
  195. -- Get a copy of `source`'s metatable, since the hacky method
  196. -- we're using to copy the table doesn't include its metatable
  197. local source_mt = getmetatable( source );
  198.  
  199. -- Return a copy of `source` including its metatable
  200. return setmetatable( { unpack( source ) }, source_mt );
  201. end;
  202.  
  203. function _getAllDescendants( Parent )
  204. -- Recursively gets all the descendants of `Parent` and returns them
  205.  
  206. local descendants = {};
  207.  
  208. for _, Child in pairs( Parent:GetChildren() ) do
  209.  
  210. -- Add the direct descendants of `Parent`
  211. table.insert( descendants, Child );
  212.  
  213. -- Add the descendants of each child
  214. for _, Subchild in pairs( _getAllDescendants( Child ) ) do
  215. table.insert( descendants, Subchild );
  216. end;
  217.  
  218. end;
  219.  
  220. return descendants;
  221.  
  222. end;
  223.  
  224. function _pointToScreenSpace( Point )
  225. -- Returns Vector3 `Point`'s position on the screen when rendered
  226. -- (kudos to stravant for this)
  227.  
  228. local point = Services.Workspace.CurrentCamera.CoordinateFrame:pointToObjectSpace( Point );
  229. local aspectRatio = Mouse.ViewSizeX / Mouse.ViewSizeY;
  230. local hfactor = math.tan( math.rad( Services.Workspace.CurrentCamera.FieldOfView ) / 2 )
  231. local wfactor = aspectRatio * hfactor;
  232.  
  233. local x = ( point.x / point.z ) / -wfactor;
  234. local y = ( point.y / point.z ) / hfactor;
  235.  
  236. local screen_pos = Vector2.new( Mouse.ViewSizeX * ( 0.5 + 0.5 * x ), Mouse.ViewSizeY * ( 0.5 + 0.5 * y ) );
  237. if ( screen_pos.x < 0 or screen_pos.x > Mouse.ViewSizeX ) or ( screen_pos.y < 0 or screen_pos.y > Mouse.ViewSizeY ) then
  238. return nil;
  239. end;
  240. if Services.Workspace.CurrentCamera.CoordinateFrame:toObjectSpace( CFrame.new( Point ) ).z > 0 then
  241. return nil;
  242. end;
  243.  
  244. return screen_pos;
  245.  
  246. end;
  247.  
  248. function _cloneParts( parts )
  249. -- Returns a table of cloned `parts`
  250.  
  251. local new_parts = {};
  252.  
  253. -- Copy the parts into `new_parts`
  254. for part_index, Part in pairs( parts ) do
  255. new_parts[part_index] = Part:Clone();
  256. end;
  257.  
  258. return new_parts;
  259. end;
  260.  
  261. function _replaceParts( old_parts, new_parts )
  262. -- Removes `old_parts` and inserts `new_parts`
  263.  
  264. -- Remove old parts
  265. for _, OldPart in pairs( old_parts ) do
  266. OldPart.Parent = nil;
  267. end;
  268.  
  269. -- Insert `new_parts
  270. for _, NewPart in pairs( new_parts ) do
  271. NewPart.Parent = Services.Workspace;
  272. NewPart:MakeJoints();
  273. end;
  274.  
  275. end;
  276.  
  277. function _splitString( str, delimiter )
  278. -- Returns a table of string `str` split by pattern `delimiter`
  279.  
  280. local parts = {};
  281. local pattern = ( "([^%s]+)" ):format( delimiter );
  282.  
  283. str:gsub( pattern, function ( part )
  284. table.insert( parts, part );
  285. end );
  286.  
  287. return parts;
  288. end;
  289.  
  290. function _generateSerializationID()
  291. -- Returns a random 5-character string
  292. -- with characters A-Z, a-z, and 0-9
  293. -- (there are 916,132,832 unique IDs)
  294.  
  295. local characters = {
  296. "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  297. "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
  298. "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
  299.  
  300. local serialization_id = "";
  301.  
  302. -- Pick out 5 random characters
  303. for _ = 1, 5 do
  304. serialization_id = serialization_id .. ( characters[math.random( #characters )] );
  305. end;
  306.  
  307. return serialization_id;
  308. end;
  309.  
  310. function _splitNumberListString( str )
  311. -- Returns the contents of _splitString( str, ", " ), except
  312. -- each value in the table is turned into a number
  313.  
  314. -- Get the number strings
  315. local numbers = _splitString( str, ", " );
  316.  
  317. -- Turn them into numbers
  318. for number_index, number in pairs( numbers ) do
  319. numbers[number_index] = tonumber( number );
  320. end;
  321.  
  322. -- Return `numbers`
  323. return numbers;
  324. end;
  325.  
  326. function _getSerializationPartType( Part )
  327. -- Returns a special number that determines the type of
  328. -- part `Part` is
  329.  
  330. local Types = {
  331. Normal = 1,
  332. Truss = 2,
  333. Wedge = 3,
  334. Corner = 4,
  335. Cylinder = 5,
  336. Ball = 6,
  337. Seat = 7,
  338. VehicleSeat = 8,
  339. Spawn = 9
  340. };
  341.  
  342. -- Return the appropriate type number
  343. if Part.ClassName == "Part" then
  344. if Part.Shape == Enum.PartType.Block then
  345. return Types.Normal;
  346. elseif Part.Shape == Enum.PartType.Cylinder then
  347. return Types.Cylinder;
  348. elseif Part.Shape == Enum.PartType.Ball then
  349. return Types.Ball;
  350. end;
  351.  
  352. elseif Part.ClassName == "Seat" then
  353. return Types.Seat;
  354.  
  355. elseif Part.ClassName == "VehicleSeat" then
  356. return Types.VehicleSeat;
  357.  
  358. elseif Part.ClassName == "SpawnLocation" then
  359. return Types.Spawn;
  360.  
  361. elseif Part.ClassName == "WedgePart" then
  362. return Types.Wedge;
  363.  
  364. elseif Part.ClassName == "CornerWedgePart" then
  365. return Types.Corner;
  366.  
  367. elseif Part.ClassName == "TrussPart" then
  368. return Types.Truss;
  369.  
  370. end;
  371.  
  372. end;
  373.  
  374. function _serializeParts( parts )
  375. -- Returns JSON-encoded data about parts in
  376. -- table `parts` that can be used to recreate them
  377.  
  378. local data = {
  379. version = 1,
  380. parts = {}
  381. };
  382.  
  383. local objects = {};
  384.  
  385. -- Store part data
  386. for _, Part in pairs( parts ) do
  387. local part_id = _generateSerializationID();
  388. local PartData = {
  389. _getSerializationPartType( Part ),
  390. _splitNumberListString( tostring( Part.Size ) ),
  391. _splitNumberListString( tostring( Part.CFrame ) ),
  392. Part.BrickColor.Number,
  393. Part.Material.Value,
  394. Part.Anchored,
  395. Part.CanCollide,
  396. Part.Reflectance,
  397. Part.Transparency,
  398. Part.TopSurface.Value,
  399. Part.BottomSurface.Value,
  400. Part.LeftSurface.Value,
  401. Part.RightSurface.Value,
  402. Part.FrontSurface.Value,
  403. Part.BackSurface.Value
  404. };
  405. data.parts[part_id] = PartData;
  406. objects[part_id] = Part;
  407. end;
  408.  
  409. -- Get any welds in the selection
  410. local welds = {};
  411. for object_id, Object in pairs( objects ) do
  412. if Object:IsA( "BasePart" ) then
  413. for _, Joint in pairs( _getAllDescendants( Services.Workspace ) ) do
  414. if Joint:IsA( "Weld" ) and Joint.Name == "BTWeld" then
  415. if Joint.Part0 == Object and #_findTableOccurrences( objects, Joint.Part1 ) > 0 then
  416. table.insert( welds, Joint );
  417. end;
  418. end;
  419. end;
  420. end;
  421. end;
  422.  
  423. -- Serialize any welds
  424. if #welds > 0 then
  425. data.welds = {};
  426. for _, Weld in pairs( welds ) do
  427. local weld_id = _generateSerializationID();
  428. local WeldData = {
  429. _findTableOccurrences( objects, Weld.Part0 )[1],
  430. _findTableOccurrences( objects, Weld.Part1 )[1],
  431. _splitNumberListString( tostring( Weld.C1 ) )
  432. };
  433. data.welds[weld_id] = WeldData;
  434. objects[weld_id] = Weld;
  435. end;
  436. end;
  437.  
  438. -- Get any meshes in the selection
  439. local meshes = {};
  440. for _, Part in pairs( parts ) do
  441. local Mesh = _getChildOfClass( Part, "SpecialMesh" );
  442. if Mesh then
  443. table.insert( meshes, Mesh );
  444. end;
  445. end;
  446.  
  447. -- Serialize any meshes
  448. if #meshes > 0 then
  449. data.meshes = {};
  450. for _, Mesh in pairs( meshes ) do
  451. local mesh_id = _generateSerializationID();
  452. local MeshData = {
  453. _findTableOccurrences( objects, Mesh.Parent )[1],
  454. Mesh.MeshType.Value,
  455. _splitNumberListString( tostring( Mesh.Scale ) ),
  456. Mesh.MeshId,
  457. Mesh.TextureId,
  458. _splitNumberListString( tostring( Mesh.VertexColor ) )
  459. };
  460. data.meshes[mesh_id] = MeshData;
  461. objects[mesh_id] = Mesh;
  462. end;
  463. end;
  464.  
  465. -- Get any textures in the selection
  466. local textures = {};
  467. for _, Part in pairs( parts ) do
  468. local textures_found = _getChildrenOfClass( Part, "Texture" );
  469. for _, Texture in pairs( textures_found ) do
  470. table.insert( textures, Texture );
  471. end;
  472. local decals_found = _getChildrenOfClass( Part, "Decal" );
  473. for _, Decal in pairs( decals_found ) do
  474. table.insert( textures, Decal );
  475. end;
  476. end;
  477.  
  478. -- Serialize any textures
  479. if #textures > 0 then
  480. data.textures = {};
  481. for _, Texture in pairs( textures ) do
  482. local texture_type;
  483. if Texture.ClassName == "Decal" then
  484. texture_type = 1;
  485. elseif Texture.ClassName == "Texture" then
  486. texture_type = 2;
  487. end;
  488. local texture_id = _generateSerializationID();
  489. local TextureData = {
  490. _findTableOccurrences( objects, Texture.Parent )[1],
  491. texture_type,
  492. Texture.Face.Value,
  493. Texture.Texture,
  494. Texture.Transparency,
  495. texture_type == 2 and Texture.StudsPerTileU or nil,
  496. texture_type == 2 and Texture.StudsPerTileV or nil
  497. };
  498. data.textures[texture_id] = TextureData;
  499. objects[texture_id] = Texture;
  500. end;
  501. end;
  502.  
  503. -- Get any lights in the selection
  504. local lights = {};
  505. for _, Part in pairs( parts ) do
  506. local lights_found = _getChildrenOfClass( Part, "Light", true );
  507. for _, Light in pairs( lights_found ) do
  508. table.insert( lights, Light );
  509. end;
  510. end;
  511.  
  512. -- Serialize any lights
  513. if #lights > 0 then
  514. data.lights = {};
  515. for _, Light in pairs( lights ) do
  516. local light_type;
  517. if Light:IsA( "PointLight" ) then
  518. light_type = 1;
  519. elseif Light:IsA( "SpotLight" ) then
  520. light_type = 2;
  521. end;
  522. local light_id = _generateSerializationID();
  523. local LightData = {
  524. _findTableOccurrences( objects, Light.Parent )[1];
  525. light_type,
  526. _splitNumberListString( tostring( Light.Color ) ),
  527. Light.Brightness,
  528. Light.Range,
  529. Light.Shadows,
  530. light_type == 2 and Light.Angle or nil,
  531. light_type == 2 and Light.Face.Value or nil
  532. };
  533. data.lights[light_id] = LightData;
  534. objects[light_id] = Light;
  535. end;
  536. end;
  537.  
  538. -- Get any decorations in the selection
  539. local decorations = {};
  540. for _, Part in pairs( parts ) do
  541. table.insert( decorations, _getChildOfClass( Part, 'Smoke' ) )
  542. table.insert( decorations, _getChildOfClass( Part, 'Fire' ) );
  543. table.insert( decorations, _getChildOfClass( Part, 'Sparkles' ) );
  544. end;
  545.  
  546. -- Serialize any decorations
  547. if #decorations > 0 then
  548. data.decorations = {};
  549. for _, Decoration in pairs( decorations ) do
  550. local decoration_type;
  551. if Decoration:IsA( 'Smoke' ) then
  552. decoration_type = 1;
  553. elseif Decoration:IsA( 'Fire' ) then
  554. decoration_type = 2;
  555. elseif Decoration:IsA( 'Sparkles' ) then
  556. decoration_type = 3;
  557. end;
  558. local decoration_id = _generateSerializationID();
  559. local DecorationData = {
  560. _findTableOccurrences( objects, Decoration.Parent )[1],
  561. decoration_type
  562. };
  563. if decoration_type == 1 then
  564. DecorationData[3] = _splitNumberListString( tostring( Decoration.Color ) );
  565. DecorationData[4] = Decoration.Opacity;
  566. DecorationData[5] = Decoration.RiseVelocity;
  567. DecorationData[6] = Decoration.Size;
  568. elseif decoration_type == 2 then
  569. DecorationData[3] = _splitNumberListString( tostring( Decoration.Color ) );
  570. DecorationData[4] = _splitNumberListString( tostring( Decoration.SecondaryColor ) );
  571. DecorationData[5] = Decoration.Heat;
  572. DecorationData[6] = Decoration.Size;
  573. elseif decoration_type == 3 then
  574. DecorationData[3] = _splitNumberListString( tostring( Decoration.SparkleColor ) );
  575. end;
  576. data.decorations[decoration_id] = DecorationData;
  577. objects[decoration_id] = Decoration;
  578. end;
  579. end;
  580.  
  581. return RbxUtility.EncodeJSON( data );
  582.  
  583. end;
  584.  
  585. function _getChildOfClass( Parent, class_name, inherit )
  586. -- Returns the first child of `Parent` that is of class `class_name`
  587. -- or nil if it couldn't find any
  588.  
  589. -- Look for a child of `Parent` of class `class_name` and return it
  590. if not inherit then
  591. for _, Child in pairs( Parent:GetChildren() ) do
  592. if Child.ClassName == class_name then
  593. return Child;
  594. end;
  595. end;
  596. else
  597. for _, Child in pairs( Parent:GetChildren() ) do
  598. if Child:IsA( class_name ) then
  599. return Child;
  600. end;
  601. end;
  602. end;
  603.  
  604. return nil;
  605.  
  606. end;
  607.  
  608. function _getChildrenOfClass( Parent, class_name, inherit )
  609. -- Returns a table containing the children of `Parent` that are
  610. -- of class `class_name`
  611. local matches = {};
  612.  
  613.  
  614. if not inherit then
  615. for _, Child in pairs( Parent:GetChildren() ) do
  616. if Child.ClassName == class_name then
  617. table.insert( matches, Child );
  618. end;
  619. end;
  620. else
  621. for _, Child in pairs( Parent:GetChildren() ) do
  622. if Child:IsA( class_name ) then
  623. table.insert( matches, Child );
  624. end;
  625. end;
  626. end;
  627.  
  628. return matches;
  629. end;
  630.  
  631. function _HSVToRGB( hue, saturation, value )
  632. -- Returns the RGB equivalent of the given HSV-defined color
  633. -- (adapted from some code found around the web)
  634.  
  635. -- If it's achromatic, just return the value
  636. if saturation == 0 then
  637. return value;
  638. end;
  639.  
  640. -- Get the hue sector
  641. local hue_sector = math.floor( hue / 60 );
  642. local hue_sector_offset = ( hue / 60 ) - hue_sector;
  643.  
  644. local p = value * ( 1 - saturation );
  645. local q = value * ( 1 - saturation * hue_sector_offset );
  646. local t = value * ( 1 - saturation * ( 1 - hue_sector_offset ) );
  647.  
  648. if hue_sector == 0 then
  649. return value, t, p;
  650. elseif hue_sector == 1 then
  651. return q, value, p;
  652. elseif hue_sector == 2 then
  653. return p, value, t;
  654. elseif hue_sector == 3 then
  655. return p, q, value;
  656. elseif hue_sector == 4 then
  657. return t, p, value;
  658. elseif hue_sector == 5 then
  659. return value, p, q;
  660. end;
  661. end;
  662.  
  663. function _RGBToHSV( red, green, blue )
  664. -- Returns the HSV equivalent of the given RGB-defined color
  665. -- (adapted from some code found around the web)
  666.  
  667. local hue, saturation, value;
  668.  
  669. local min_value = math.min( red, green, blue );
  670. local max_value = math.max( red, green, blue );
  671.  
  672. value = max_value;
  673.  
  674. local value_delta = max_value - min_value;
  675.  
  676. -- If the color is not black
  677. if max_value ~= 0 then
  678. saturation = value_delta / max_value;
  679.  
  680. -- If the color is purely black
  681. else
  682. saturation = 0;
  683. hue = -1;
  684. return hue, saturation, value;
  685. end;
  686.  
  687. if red == max_value then
  688. hue = ( green - blue ) / value_delta;
  689. elseif green == max_value then
  690. hue = 2 + ( blue - red ) / value_delta;
  691. else
  692. hue = 4 + ( red - green ) / value_delta;
  693. end;
  694.  
  695. hue = hue * 60;
  696. if hue < 0 then
  697. hue = hue + 360;
  698. end;
  699.  
  700. return hue, saturation, value;
  701. end;
  702.  
  703. ------------------------------------------
  704. -- Create data containers
  705. ------------------------------------------
  706. ActiveKeys = {};
  707.  
  708. CurrentTool = nil;
  709.  
  710. function equipTool( NewTool )
  711.  
  712. -- If it's a different tool than the current one
  713. if CurrentTool ~= NewTool then
  714.  
  715. -- Run (if existent) the old tool's `Unequipped` listener
  716. if CurrentTool and CurrentTool.Listeners.Unequipped then
  717. CurrentTool.Listeners.Unequipped();
  718. end;
  719.  
  720. CurrentTool = NewTool;
  721.  
  722. -- Recolor the handle
  723. if ToolType == 'tool' then
  724. Tool.Handle.BrickColor = NewTool.Color;
  725. end;
  726.  
  727. -- Highlight the right button on the dock
  728. for _, Button in pairs( Dock.ToolButtons:GetChildren() ) do
  729. Button.BackgroundTransparency = 1;
  730. end;
  731. local Button = Dock.ToolButtons:FindFirstChild( getToolName( NewTool ) .. "Button" );
  732. if Button then
  733. Button.BackgroundTransparency = 0;
  734. end;
  735.  
  736. -- Run (if existent) the new tool's `Equipped` listener
  737. if NewTool.Listeners.Equipped then
  738. NewTool.Listeners.Equipped();
  739. end;
  740.  
  741. end;
  742. end;
  743.  
  744. function cloneSelection()
  745. -- Clones the items in the selection
  746.  
  747. -- Make sure that there are items in the selection
  748. if #Selection.Items > 0 then
  749.  
  750. local item_copies = {};
  751.  
  752. -- Make a copy of every item in the selection and add it to table `item_copies`
  753. for _, Item in pairs( Selection.Items ) do
  754. local ItemCopy = Item:Clone();
  755. ItemCopy.Parent = Services.Workspace;
  756. table.insert( item_copies, ItemCopy );
  757. end;
  758.  
  759. -- Replace the selection with the copied items
  760. Selection:clear();
  761. for _, Item in pairs( item_copies ) do
  762. Selection:add( Item );
  763. end;
  764.  
  765. local HistoryRecord = {
  766. copies = item_copies;
  767. unapply = function ( self )
  768. for _, Copy in pairs( self.copies ) do
  769. if Copy then
  770. Copy.Parent = nil;
  771. end;
  772. end;
  773. end;
  774. apply = function ( self )
  775. Selection:clear();
  776. for _, Copy in pairs( self.copies ) do
  777. if Copy then
  778. Copy.Parent = Services.Workspace;
  779. Copy:MakeJoints();
  780. Selection:add( Copy );
  781. end;
  782. end;
  783. end;
  784. };
  785. History:add( HistoryRecord );
  786.  
  787. -- Play a confirmation sound
  788. local Sound = RbxUtility.Create "Sound" {
  789. Name = "BTActionCompletionSound";
  790. Pitch = 1.5;
  791. SoundId = action_completion_sound;
  792. Volume = 1;
  793. Parent = Player or Services.SoundService;
  794. };
  795. Sound:Play();
  796. Sound:Destroy();
  797.  
  798. -- Highlight the outlines of the new parts
  799. coroutine.wrap( function ()
  800. for transparency = 1, 0.5, -0.1 do
  801. for Item, SelectionBox in pairs( SelectionBoxes ) do
  802. SelectionBox.Transparency = transparency;
  803. end;
  804. wait( 0.1 );
  805. end;
  806. end )();
  807.  
  808. end;
  809.  
  810. end;
  811.  
  812. function deleteSelection()
  813. -- Deletes the items in the selection
  814.  
  815. if #Selection.Items == 0 then
  816. return;
  817. end;
  818.  
  819. local selection_items = _cloneTable( Selection.Items );
  820.  
  821. -- Create a history record
  822. local HistoryRecord = {
  823. targets = selection_items;
  824. parents = {};
  825. apply = function ( self )
  826. for _, Target in pairs( self.targets ) do
  827. if Target then
  828. Target.Parent = nil;
  829. end;
  830. end;
  831. end;
  832. unapply = function ( self )
  833. Selection:clear();
  834. for _, Target in pairs( self.targets ) do
  835. if Target then
  836. Target.Parent = self.parents[Target];
  837. Target:MakeJoints();
  838. Selection:add( Target );
  839. end;
  840. end;
  841. end;
  842. };
  843.  
  844. for _, Item in pairs( selection_items ) do
  845. HistoryRecord.parents[Item] = Item.Parent;
  846. Item.Parent = nil;
  847. end;
  848.  
  849. History:add( HistoryRecord );
  850.  
  851. end;
  852.  
  853. function prismSelect()
  854. -- Selects all the parts within the area of the selected parts
  855.  
  856. -- Make sure parts to define the area are present
  857. if #Selection.Items == 0 then
  858. return;
  859. end;
  860.  
  861. local parts = {};
  862.  
  863. -- Get all the parts in workspace
  864. local workspace_parts = {};
  865. local workspace_children = _getAllDescendants( Services.Workspace );
  866. for _, Child in pairs( workspace_children ) do
  867. if Child:IsA( 'BasePart' ) and not Selection:find( Child ) then
  868. table.insert( workspace_parts, Child );
  869. end;
  870. end;
  871.  
  872. -- Go through each part and perform area tests on each one
  873. local checks = {};
  874. for _, Item in pairs( workspace_parts ) do
  875. checks[Item] = 0;
  876. for _, SelectionItem in pairs( Selection.Items ) do
  877.  
  878. -- Calculate the position of the item in question in relation to the area-defining parts
  879. local offset = SelectionItem.CFrame:toObjectSpace( Item.CFrame );
  880. local extents = SelectionItem.Size / 2;
  881.  
  882. -- Check the item off if it passed this test (if it's within the range of the extents)
  883. if ( math.abs( offset.x ) <= extents.x ) and ( math.abs( offset.y ) <= extents.y ) and ( math.abs( offset.z ) <= extents.z ) then
  884. checks[Item] = checks[Item] + 1;
  885. end;
  886.  
  887. end;
  888. end;
  889.  
  890. -- Delete the parts that were used to select the area
  891. local selection_items = _cloneTable( Selection.Items );
  892. local selection_item_parents = {};
  893. for _, Item in pairs( selection_items ) do
  894. selection_item_parents[Item] = Item.Parent;
  895. Item.Parent = nil;
  896. end;
  897.  
  898. -- Select the parts that passed any area checks
  899. for _, Item in pairs( workspace_parts ) do
  900. if checks[Item] > 0 then
  901. Selection:add( Item );
  902. end;
  903. end;
  904.  
  905. -- Add a history record
  906. History:add( {
  907. selection_parts = selection_items;
  908. selection_part_parents = selection_item_parents;
  909. new_selection = _cloneTable( Selection.Items );
  910. apply = function ( self )
  911. Selection:clear();
  912. for _, Item in pairs( self.selection_parts ) do
  913. Item.Parent = nil;
  914. end;
  915. for _, Item in pairs( self.new_selection ) do
  916. Selection:add( Item );
  917. end;
  918. end;
  919. unapply = function ( self )
  920. Selection:clear();
  921. for _, Item in pairs( self.selection_parts ) do
  922. Item.Parent = self.selection_part_parents[Item];
  923. Selection:add( Item );
  924. end;
  925. end;
  926. } );
  927.  
  928. end;
  929.  
  930. function toggleHelp()
  931.  
  932. -- Make sure the dock is ready
  933. if not Dock then
  934. return;
  935. end;
  936.  
  937. -- Toggle the visibility of the help tooltip
  938. Dock.HelpInfo.Visible = not Dock.HelpInfo.Visible;
  939.  
  940. end;
  941.  
  942. function getToolName( tool )
  943. -- Returns the name of `tool` as registered in `Tools`
  944.  
  945. local name_search = _findTableOccurrences( Tools, tool );
  946. if #name_search > 0 then
  947. return name_search[1];
  948. end;
  949.  
  950. end;
  951.  
  952. function isSelectable( Object )
  953. -- Returns whether `Object` is selectable
  954.  
  955. if not Object or not Object.Parent or not Object:IsA( "BasePart" ) or Object.Locked or Selection:find( Object ) then
  956. return false;
  957. end;
  958.  
  959. -- If it passes all checks, return true
  960. return true;
  961. end;
  962.  
  963. UpdateNotificationShown = false;
  964. function ShowUpdateNotification()
  965. -- Displays a notification if there's a new update to the tool
  966.  
  967. -- Make sure that the notification hasn't already been shown
  968. if UpdateNotificationShown then
  969. return;
  970. end;
  971.  
  972. -- Check the most recent version number
  973. local AssetInfo = Services.MarketplaceService:GetProductInfo( ToolAssetID, Enum.InfoType.Asset );
  974. local VersionID = AssetInfo.Description:match( '%[Version: (.+)%]' );
  975. local CurrentVersionID = ( Tool:WaitForChild 'Version' ).Value;
  976.  
  977. -- If the most recent version ID differs from the current tool's version ID,
  978. -- notify the user that this version is outdated
  979. if VersionID ~= CurrentVersionID then
  980. -- Display the notification and hide it after a while
  981. local Notification = Tool.Interfaces.BTUpdateNotification:Clone();
  982. Notification.Parent = Dock;
  983. UpdateNotificationShown = true;
  984. Services.Debris:AddItem( Notification, 4 );
  985. end;
  986. end;
  987.  
  988. -- Keep some state data
  989. clicking = false;
  990. selecting = false;
  991. click_x, click_y = 0, 0;
  992. override_selection = false;
  993.  
  994. SelectionBoxes = {};
  995. SelectionExistenceListeners = {};
  996. SelectionBoxColor = BrickColor.new( "Cyan" );
  997. TargetBox = nil;
  998.  
  999. -- Keep a container for temporary connections
  1000. -- from the platform
  1001. Connections = {};
  1002.  
  1003. -- Make sure the UI container gets placed
  1004. UI = RbxUtility.Create "ScreenGui" {
  1005. Name = "Building Tools by F3X (UI)"
  1006. };
  1007. if ToolType == 'tool' then
  1008. UI.Parent = GUIContainer;
  1009. elseif ToolType == 'plugin' then
  1010. UI.Parent = Services.CoreGui;
  1011. end;
  1012.  
  1013. Dragger = nil;
  1014.  
  1015. function updateSelectionBoxColor()
  1016. -- Updates the color of the selectionboxes
  1017. for _, SelectionBox in pairs( SelectionBoxes ) do
  1018. SelectionBox.Color = SelectionBoxColor;
  1019. end;
  1020. end;
  1021.  
  1022. Selection = {
  1023.  
  1024. ["Items"] = {};
  1025.  
  1026. -- Provide events to listen to changes in the selection
  1027. ["Changed"] = RbxUtility.CreateSignal();
  1028. ["ItemAdded"] = RbxUtility.CreateSignal();
  1029. ["ItemRemoved"] = RbxUtility.CreateSignal();
  1030.  
  1031. -- Provide a method to get an item's index in the selection
  1032. ["find"] = function ( self, Needle )
  1033.  
  1034. -- Look through all the selected items and return the matching item's index
  1035. for item_index, Item in pairs( self.Items ) do
  1036. if Item == Needle then
  1037. return item_index;
  1038. end;
  1039. end;
  1040.  
  1041. -- Otherwise, return `nil`
  1042.  
  1043. end;
  1044.  
  1045. -- Provide a method to add items to the selection
  1046. ["add"] = function ( self, NewPart )
  1047.  
  1048. -- Make sure `NewPart` is selectable
  1049. if not isSelectable( NewPart ) then
  1050. return false;
  1051. end;
  1052.  
  1053. -- Make sure `NewPart` isn't already in the selection
  1054. if #_findTableOccurrences( self.Items, NewPart ) > 0 then
  1055. return false;
  1056. end;
  1057.  
  1058. -- Insert it into the selection
  1059. table.insert( self.Items, NewPart );
  1060.  
  1061. -- Add its SelectionBox
  1062. SelectionBoxes[NewPart] = Instance.new( "SelectionBox", UI );
  1063. SelectionBoxes[NewPart].Name = "BTSelectionBox";
  1064. SelectionBoxes[NewPart].Color = SelectionBoxColor;
  1065. SelectionBoxes[NewPart].Adornee = NewPart;
  1066. SelectionBoxes[NewPart].Transparency = 0.5;
  1067.  
  1068. -- Remove any target selection box focus
  1069. if NewPart == TargetBox.Adornee then
  1070. TargetBox.Adornee = nil;
  1071. end;
  1072.  
  1073. -- Make sure to remove the item from the selection when it's deleted
  1074. SelectionExistenceListeners[NewPart] = NewPart.AncestryChanged:connect( function ( Object, NewParent )
  1075. if NewParent == nil then
  1076. Selection:remove( NewPart );
  1077. end;
  1078. end );
  1079.  
  1080. -- Provide a reference to the last item added to the selection (i.e. NewPart)
  1081. self:focus( NewPart );
  1082.  
  1083. -- Fire events
  1084. self.ItemAdded:fire( NewPart );
  1085. self.Changed:fire();
  1086.  
  1087. end;
  1088.  
  1089. -- Provide a method to remove items from the selection
  1090. ["remove"] = function ( self, Item )
  1091.  
  1092. -- Make sure selection item `Item` exists
  1093. if not self:find( Item ) then
  1094. return false;
  1095. end;
  1096.  
  1097. -- Remove `Item`'s SelectionBox
  1098. local SelectionBox = SelectionBoxes[Item];
  1099. if SelectionBox then
  1100. SelectionBox:Destroy();
  1101. end;
  1102. SelectionBoxes[Item] = nil;
  1103.  
  1104. -- Delete the item from the selection
  1105. table.remove( self.Items, self:find( Item ) );
  1106.  
  1107. -- If it was logged as the last item, change it
  1108. if self.Last == Item then
  1109. self:focus( ( #self.Items > 0 ) and self.Items[#self.Items] or nil );
  1110. end;
  1111.  
  1112. -- Delete the existence listeners of the item
  1113. SelectionExistenceListeners[Item]:disconnect();
  1114. SelectionExistenceListeners[Item] = nil;
  1115.  
  1116. -- Fire events
  1117. self.ItemRemoved:fire( Item );
  1118. self.Changed:fire();
  1119.  
  1120. end;
  1121.  
  1122. -- Provide a method to clear the selection
  1123. ["clear"] = function ( self )
  1124.  
  1125. -- Go through all the items in the selection and call `self.remove` on them
  1126. for _, Item in pairs( _cloneTable( self.Items ) ) do
  1127. self:remove( Item );
  1128. end;
  1129.  
  1130. end;
  1131.  
  1132. -- Provide a method to change the focus of the selection
  1133. ["focus"] = function ( self, NewFocus )
  1134.  
  1135. -- Change the focus
  1136. self.Last = NewFocus;
  1137.  
  1138. -- Fire events
  1139. self.Changed:fire();
  1140.  
  1141. end;
  1142.  
  1143. };
  1144.  
  1145. -- Keep the Studio selection up-to-date (if applicable)
  1146. if ToolType == 'plugin' then
  1147. Selection.Changed:connect( function ()
  1148. Services.Selection:Set( Selection.Items );
  1149. end );
  1150. end;
  1151.  
  1152. Tools = {};
  1153.  
  1154. ------------------------------------------
  1155. -- Define other utilities needed by tools
  1156. ------------------------------------------
  1157.  
  1158. function createDropdown()
  1159.  
  1160. local Frame = RbxUtility.Create "Frame" {
  1161. Name = "Dropdown";
  1162. Size = UDim2.new( 0, 20, 0, 20 );
  1163. BackgroundTransparency = 1;
  1164. BorderSizePixel = 0;
  1165. ClipsDescendants = true;
  1166. };
  1167.  
  1168. RbxUtility.Create "ImageLabel" {
  1169. Parent = Frame;
  1170. Name = "Arrow";
  1171. BackgroundTransparency = 1;
  1172. BorderSizePixel = 0;
  1173. Image = expand_arrow;
  1174. Position = UDim2.new( 1, -21, 0, 3 );
  1175. Size = UDim2.new( 0, 20, 0, 20 );
  1176. ZIndex = 3;
  1177. };
  1178.  
  1179. local DropdownObject = {
  1180. -- Provide access to the actual frame
  1181. Frame = Frame;
  1182.  
  1183. -- Keep a list of all the options in the dropdown
  1184. _options = {};
  1185.  
  1186. -- Provide a function to add options to the dropdown
  1187. addOption = function ( self, option )
  1188.  
  1189. -- Add the option to the list
  1190. table.insert( self._options, option );
  1191.  
  1192. -- Create the GUI for the option
  1193. local Button = RbxUtility.Create "TextButton" {
  1194. Parent = self.Frame;
  1195. BackgroundColor3 = Color3.new( 0, 0, 0 );
  1196. BackgroundTransparency = 0.3;
  1197. BorderColor3 = Color3.new( 27 / 255, 42 / 255, 53 / 255 );
  1198. BorderSizePixel = 1;
  1199. Name = option;
  1200. Position = UDim2.new( math.ceil( #self._options / 9 ) - 1, 0, 0, 25 * ( ( #self._options % 9 == 0 ) and 9 or ( #self._options % 9 ) ) );
  1201. Size = UDim2.new( 1, 0, 0, 25 );
  1202. ZIndex = 3;
  1203. Text = "";
  1204. };
  1205. local Label = RbxUtility.Create "TextLabel" {
  1206. Parent = Button;
  1207. BackgroundTransparency = 1;
  1208. BorderSizePixel = 0;
  1209. Position = UDim2.new( 0, 6, 0, 0 );
  1210. Size = UDim2.new( 1, -30, 1, 0 );
  1211. ZIndex = 3;
  1212. Font = Enum.Font.ArialBold;
  1213. FontSize = Enum.FontSize.Size12;
  1214. Text = option;
  1215. TextColor3 = Color3.new( 1, 1, 1 );
  1216. TextXAlignment = Enum.TextXAlignment.Left;
  1217. TextYAlignment = Enum.TextYAlignment.Center;
  1218. };
  1219.  
  1220. -- Return the button object
  1221. return Button;
  1222.  
  1223. end;
  1224.  
  1225. selectOption = function ( self, option )
  1226. self.Frame.MainButton.CurrentOption.Text = option;
  1227. end;
  1228.  
  1229. open = false;
  1230.  
  1231. toggle = function ( self )
  1232.  
  1233. -- If it's open, close it
  1234. if self.open then
  1235. self.Frame.MainButton.BackgroundTransparency = 0.3;
  1236. self.Frame.ClipsDescendants = true;
  1237. self.open = false;
  1238.  
  1239. -- If it's not open, open it
  1240. else
  1241. self.Frame.MainButton.BackgroundTransparency = 0;
  1242. self.Frame.ClipsDescendants = false;
  1243. self.open = true;
  1244. end;
  1245.  
  1246. end;
  1247.  
  1248. };
  1249.  
  1250. -- Create the GUI for the option
  1251. local MainButton = RbxUtility.Create "TextButton" {
  1252. Parent = Frame;
  1253. Name = "MainButton";
  1254. BackgroundColor3 = Color3.new( 0, 0, 0 );
  1255. BackgroundTransparency = 0.3;
  1256. BorderColor3 = Color3.new( 27 / 255, 42 / 255, 53 / 255 );
  1257. BorderSizePixel = 1;
  1258. Position = UDim2.new( 0, 0, 0, 0 );
  1259. Size = UDim2.new( 1, 0, 0, 25 );
  1260. ZIndex = 2;
  1261. Text = "";
  1262.  
  1263. -- Toggle the dropdown when pressed
  1264. [RbxUtility.Create.E "MouseButton1Up"] = function ()
  1265. DropdownObject:toggle();
  1266. end;
  1267. };
  1268. RbxUtility.Create "TextLabel" {
  1269. Parent = MainButton;
  1270. Name = "CurrentOption";
  1271. BackgroundTransparency = 1;
  1272. BorderSizePixel = 0;
  1273. Position = UDim2.new( 0, 6, 0, 0 );
  1274. Size = UDim2.new( 1, -30, 1, 0 );
  1275. ZIndex = 3;
  1276. Font = Enum.Font.ArialBold;
  1277. FontSize = Enum.FontSize.Size12;
  1278. Text = "";
  1279. TextColor3 = Color3.new( 1, 1, 1 );
  1280. TextXAlignment = Enum.TextXAlignment.Left;
  1281. TextYAlignment = Enum.TextYAlignment.Center;
  1282. };
  1283.  
  1284. return DropdownObject;
  1285.  
  1286. end;
  1287.  
  1288. ------------------------------------------
  1289. -- Provide an interface to the 2D
  1290. -- selection system
  1291. ------------------------------------------
  1292.  
  1293. Select2D = {
  1294.  
  1295. -- Keep state data
  1296. ["enabled"] = false;
  1297.  
  1298. -- Keep objects
  1299. ["GUI"] = nil;
  1300.  
  1301. -- Keep temporary, disposable connections
  1302. ["Connections"] = {};
  1303.  
  1304. -- Provide an interface to the functions
  1305. ["start"] = function ( self )
  1306.  
  1307. if enabled then
  1308. return;
  1309. end;
  1310.  
  1311. self.enabled = true;
  1312.  
  1313. -- Create the GUI
  1314. self.GUI = RbxUtility.Create "ScreenGui" {
  1315. Name = "BTSelectionRectangle";
  1316. Parent = UI;
  1317. };
  1318.  
  1319. local Rectangle = RbxUtility.Create "Frame" {
  1320. Name = "Rectangle";
  1321. Active = false;
  1322. Parent = self.GUI;
  1323. BackgroundColor3 = Color3.new( 0, 0, 0 );
  1324. BackgroundTransparency = 0.5;
  1325. BorderSizePixel = 0;
  1326. Position = UDim2.new( 0, math.min( click_x, Mouse.X ), 0, math.min( click_y, Mouse.Y ) );
  1327. Size = UDim2.new( 0, math.max( click_x, Mouse.X ) - math.min( click_x, Mouse.X ), 0, math.max( click_y, Mouse.Y ) - math.min( click_y, Mouse.Y ) );
  1328. };
  1329.  
  1330. -- Listen for when to resize the selection
  1331. self.Connections.SelectionResize = Mouse.Move:connect( function ()
  1332. Rectangle.Position = UDim2.new( 0, math.min( click_x, Mouse.X ), 0, math.min( click_y, Mouse.Y ) );
  1333. Rectangle.Size = UDim2.new( 0, math.max( click_x, Mouse.X ) - math.min( click_x, Mouse.X ), 0, math.max( click_y, Mouse.Y ) - math.min( click_y, Mouse.Y ) );
  1334. end );
  1335.  
  1336. -- Listen for when the selection ends
  1337. self.Connections.SelectionEnd = Mouse.Button1Up:connect( function ()
  1338. self:select();
  1339. self:finish();
  1340. end );
  1341.  
  1342. end;
  1343.  
  1344. ["select"] = function ( self )
  1345.  
  1346. if not self.enabled then
  1347. return;
  1348. end;
  1349.  
  1350. for _, Object in pairs( _getAllDescendants( Services.Workspace ) ) do
  1351.  
  1352. -- Make sure we can select this part
  1353. if isSelectable( Object ) then
  1354.  
  1355. -- Check if the part is rendered within the range of the selection area
  1356. local PartPosition = _pointToScreenSpace( Object.Position );
  1357. if PartPosition then
  1358. local left_check = PartPosition.x >= self.GUI.Rectangle.AbsolutePosition.x;
  1359. local right_check = PartPosition.x <= ( self.GUI.Rectangle.AbsolutePosition.x + self.GUI.Rectangle.AbsoluteSize.x );
  1360. local top_check = PartPosition.y >= self.GUI.Rectangle.AbsolutePosition.y;
  1361. local bottom_check = PartPosition.y <= ( self.GUI.Rectangle.AbsolutePosition.y + self.GUI.Rectangle.AbsoluteSize.y );
  1362.  
  1363. -- If the part is within the selection area, select it
  1364. if left_check and right_check and top_check and bottom_check then
  1365. Selection:add( Object );
  1366. end;
  1367. end;
  1368.  
  1369. end;
  1370.  
  1371. end;
  1372.  
  1373. end;
  1374.  
  1375. ["finish"] = function ( self )
  1376.  
  1377. if not self.enabled then
  1378. return;
  1379. end;
  1380.  
  1381. -- Disconnect temporary connections
  1382. for connection_index, Connection in pairs( self.Connections ) do
  1383. Connection:disconnect();
  1384. self.Connections[connection_index] = nil;
  1385. end;
  1386.  
  1387. -- Remove temporary objects
  1388. self.GUI:Destroy();
  1389. self.GUI = nil;
  1390.  
  1391. self.enabled = false;
  1392.  
  1393. end;
  1394.  
  1395. };
  1396.  
  1397. ------------------------------------------
  1398. -- Provide an interface to the edge
  1399. -- selection system
  1400. ------------------------------------------
  1401. SelectEdge = {
  1402.  
  1403. -- Keep state data
  1404. ["enabled"] = false;
  1405. ["started"] = false;
  1406.  
  1407. -- Keep objects
  1408. ["Marker"] = nil;
  1409. ["MarkerOutline"] = RbxUtility.Create "SelectionBox" {
  1410. Color = BrickColor.new( "Institutional white" );
  1411. Parent = UI;
  1412. Name = "BTEdgeSelectionMarkerOutline";
  1413. };
  1414.  
  1415. -- Keep temporary, disposable connections
  1416. ["Connections"] = {};
  1417.  
  1418. -- Provide an interface to the functions
  1419. ["start"] = function ( self, edgeSelectionCallback )
  1420.  
  1421. if self.started then
  1422. return;
  1423. end;
  1424.  
  1425. -- Listen for when to engage in selection
  1426. self.Connections.KeyListener = Mouse.KeyDown:connect( function ( key )
  1427.  
  1428. local key = key:lower();
  1429. local key_code = key:byte();
  1430.  
  1431. if key == "t" and #Selection.Items > 0 then
  1432. self:enable( edgeSelectionCallback );
  1433. end;
  1434.  
  1435. end );
  1436.  
  1437. self.started = true;
  1438.  
  1439. end;
  1440.  
  1441. ["enable"] = function ( self, edgeSelectionCallback )
  1442.  
  1443. if self.enabled then
  1444. return;
  1445. end;
  1446.  
  1447. self.Connections.MoveListener = Mouse.Move:connect( function ()
  1448.  
  1449. -- Make sure the target can be selected
  1450. if not Selection:find( Mouse.Target ) then
  1451. return;
  1452. end;
  1453.  
  1454. -- Calculate the proximity to each edge
  1455. local Proximity = {};
  1456. local edges = {};
  1457.  
  1458. -- Create shortcuts to certain things that are expensive to call constantly
  1459. local table_insert = table.insert;
  1460. local newCFrame = CFrame.new;
  1461. local PartCFrame = Mouse.Target.CFrame;
  1462. local partCFrameOffset = PartCFrame.toWorldSpace;
  1463. local PartSize = Mouse.Target.Size / 2;
  1464. local size_x, size_y, size_z = PartSize.x, PartSize.y, PartSize.z;
  1465.  
  1466. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, size_y, size_z ) ) );
  1467. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, size_y, size_z ) ) );
  1468. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, -size_y, size_z ) ) );
  1469. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, size_y, -size_z ) ) );
  1470. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, size_y, -size_z ) ) );
  1471. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, -size_y, size_z ) ) );
  1472. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, -size_y, -size_z ) ) );
  1473. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, -size_y, -size_z ) ) );
  1474. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, size_y, 0 ) ) );
  1475. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, 0, size_z ) ) );
  1476. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, size_y, size_z ) ) );
  1477. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, 0, 0 ) ) );
  1478. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, size_y, 0 ) ) );
  1479. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, 0, size_z ) ) );
  1480. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, size_y, 0 ) ) );
  1481. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, 0, size_z ) ) );
  1482. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, -size_y, size_z ) ) );
  1483. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, 0, 0 ) ) );
  1484. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, -size_y, 0 ) ) );
  1485. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, 0, -size_z ) ) );
  1486. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, -size_y, 0 ) ) );
  1487. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( size_x, 0, -size_z ) ) );
  1488. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, size_y, -size_z ) ) );
  1489. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, -size_y, 0 ) ) );
  1490. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( -size_x, 0, -size_z ) ) );
  1491. table_insert( edges, partCFrameOffset( PartCFrame, newCFrame( 0, -size_y, -size_z ) ) );
  1492.  
  1493. -- Calculate the proximity of every edge to the mouse
  1494. for edge_index, Edge in pairs( edges ) do
  1495. Proximity[edge_index] = ( Mouse.Hit.p - Edge.p ).magnitude;
  1496. end;
  1497.  
  1498. -- Get the closest edge to the mouse
  1499. local highest_proximity = 1;
  1500. for proximity_index, proximity in pairs( Proximity ) do
  1501. if proximity < Proximity[highest_proximity] then
  1502. highest_proximity = proximity_index;
  1503. end;
  1504. end;
  1505.  
  1506. -- Replace the current target edge (if any)
  1507. local ClosestEdge = edges[highest_proximity];
  1508.  
  1509. if self.Marker then
  1510. self.Marker:Destroy();
  1511. end;
  1512. self.Marker = RbxUtility.Create "Part" {
  1513. Name = "BTEdgeSelectionMarker";
  1514. Anchored = true;
  1515. Locked = true;
  1516. CanCollide = false;
  1517. Transparency = 1;
  1518. FormFactor = Enum.FormFactor.Custom;
  1519. Size = Vector3.new( 0.2, 0.2, 0.2 );
  1520. CFrame = ClosestEdge;
  1521. };
  1522.  
  1523. self.MarkerOutline.Adornee = self.Marker;
  1524.  
  1525. end );
  1526.  
  1527. self.Connections.ClickListener = Mouse.Button1Up:connect( function ()
  1528. override_selection = true;
  1529. self:select( edgeSelectionCallback );
  1530. end );
  1531.  
  1532. self.enabled = true;
  1533.  
  1534. end;
  1535.  
  1536. ["select"] = function ( self, callback )
  1537.  
  1538. if not self.enabled or not self.Marker then
  1539. return;
  1540. end;
  1541.  
  1542. self.MarkerOutline.Adornee = self.Marker;
  1543.  
  1544. callback( self.Marker );
  1545.  
  1546. -- Stop treating it like a marker
  1547. self.Marker = nil;
  1548.  
  1549. self:disable();
  1550.  
  1551. end;
  1552.  
  1553. ["disable"] = function ( self )
  1554.  
  1555. if not self.enabled then
  1556. return;
  1557. end;
  1558.  
  1559. -- Disconnect unnecessary temporary connections
  1560. if self.Connections.ClickListener then
  1561. self.Connections.ClickListener:disconnect();
  1562. self.Connections.ClickListener = nil;
  1563. end;
  1564. if self.Connections.MoveListener then
  1565. self.Connections.MoveListener:disconnect();
  1566. self.Connections.MoveListener = nil;
  1567. end;
  1568.  
  1569. -- Remove temporary objects
  1570. if self.Marker then
  1571. self.Marker:Destroy();
  1572. end;
  1573. self.Marker = nil;
  1574.  
  1575. self.MarkerOutline.Adornee = nil;
  1576. self.enabled = false;
  1577.  
  1578. end;
  1579.  
  1580. ["stop"] = function ( self )
  1581.  
  1582. if not self.started then
  1583. return;
  1584. end;
  1585.  
  1586. -- Cancel any ongoing selection
  1587. self:disable();
  1588.  
  1589. -- Disconnect & remove all temporary connections
  1590. for connection_index, Connection in pairs( self.Connections ) do
  1591. Connection:disconnect();
  1592. self.Connections[connection_index] = nil;
  1593. end;
  1594.  
  1595. -- Remove temporary objects
  1596. if self.Marker then
  1597. self.Marker:Destroy();
  1598. end;
  1599.  
  1600. self.started = false;
  1601.  
  1602. end;
  1603.  
  1604. };
  1605.  
  1606. ------------------------------------------
  1607. -- Provide an interface to the history
  1608. -- system
  1609. ------------------------------------------
  1610. History = {
  1611.  
  1612. -- Keep a container for the actual history data
  1613. ["Data"] = {};
  1614.  
  1615. -- Keep state data
  1616. ["index"] = 0;
  1617.  
  1618. -- Provide events for the platform to listen for changes
  1619. ["Changed"] = RbxUtility.CreateSignal();
  1620.  
  1621. -- Provide functions to control the system
  1622. ["undo"] = function ( self )
  1623.  
  1624. -- Make sure we're not getting out of boundary
  1625. if self.index - 1 < 0 then
  1626. return;
  1627. end;
  1628.  
  1629. -- Fetch the history record & unapply it
  1630. local CurrentRecord = self.Data[self.index];
  1631. CurrentRecord:unapply();
  1632.  
  1633. -- Go back in the history
  1634. self.index = self.index - 1;
  1635.  
  1636. -- Fire the relevant events
  1637. self.Changed:fire();
  1638.  
  1639. end;
  1640.  
  1641. ["redo"] = function ( self )
  1642.  
  1643. -- Make sure we're not getting out of boundary
  1644. if self.index + 1 > #self.Data then
  1645. return;
  1646. end;
  1647.  
  1648. -- Go forward in the history
  1649. self.index = self.index + 1;
  1650.  
  1651. -- Fetch the new history record & apply it
  1652. local NewRecord = self.Data[self.index];
  1653. NewRecord:apply();
  1654.  
  1655. -- Fire the relevant events
  1656. self.Changed:fire();
  1657.  
  1658. end;
  1659.  
  1660. ["add"] = function ( self, Record )
  1661.  
  1662. -- Place the record in its right spot
  1663. self.Data[self.index + 1] = Record;
  1664.  
  1665. -- Advance the history index
  1666. self.index = self.index + 1;
  1667.  
  1668. -- Clear out the following history
  1669. for index = self.index + 1, #self.Data do
  1670. self.Data[index] = nil;
  1671. end;
  1672.  
  1673. -- Fire the relevant events
  1674. self.Changed:fire();
  1675.  
  1676. end;
  1677.  
  1678. };
  1679.  
  1680.  
  1681. ------------------------------------------
  1682. -- Provide an interface color picker
  1683. -- system
  1684. ------------------------------------------
  1685. ColorPicker = {
  1686.  
  1687. -- Keep some state data
  1688. ["enabled"] = false;
  1689. ["callback"] = nil;
  1690. ["track_mouse"] = nil;
  1691. ["hue"] = 0;
  1692. ["saturation"] = 1;
  1693. ["value"] = 1;
  1694.  
  1695. -- Keep the current GUI here
  1696. ["GUI"] = nil;
  1697.  
  1698. -- Keep temporary, disposable connections here
  1699. ["Connections"] = {};
  1700.  
  1701. -- Provide an interface to the functions
  1702. ["start"] = function ( self, callback, start_color )
  1703.  
  1704. -- Replace any existing color pickers
  1705. if self.enabled then
  1706. self:cancel();
  1707. end;
  1708. self.enabled = true;
  1709.  
  1710. -- Create the GUI
  1711. self.GUI = Tool.Interfaces.BTHSVColorPicker:Clone();
  1712. self.GUI.Parent = UI;
  1713.  
  1714. -- Register the callback function for when we're done here
  1715. self.callback = callback;
  1716.  
  1717. -- Update the GUI
  1718. local start_color = start_color or Color3.new( 1, 0, 0 );
  1719. self:_changeColor( _RGBToHSV( start_color.r, start_color.g, start_color.b ) );
  1720.  
  1721. -- Add functionality to the GUI's interactive elements
  1722. table.insert( self.Connections, self.GUI.HueSaturation.MouseButton1Down:connect( function ( x, y )
  1723. self.track_mouse = 'hue-saturation';
  1724. self:_onMouseMove( x, y );
  1725. end ) );
  1726.  
  1727. table.insert( self.Connections, self.GUI.HueSaturation.MouseButton1Up:connect( function ()
  1728. self.track_mouse = nil;
  1729. end ) );
  1730.  
  1731. table.insert( self.Connections, self.GUI.MouseMoved:connect( function ( x, y )
  1732. self:_onMouseMove( x, y );
  1733. end ) );
  1734.  
  1735. table.insert( self.Connections, self.GUI.Value.MouseButton1Down:connect( function ( x, y )
  1736. self.track_mouse = 'value';
  1737. self:_onMouseMove( x, y );
  1738. end ) );
  1739.  
  1740. table.insert( self.Connections, self.GUI.Value.MouseButton1Up:connect( function ()
  1741. self.track_mouse = nil;
  1742. end ) );
  1743.  
  1744. table.insert( self.Connections, self.GUI.OkButton.MouseButton1Up:connect( function ()
  1745. self:finish();
  1746. end ) );
  1747.  
  1748. table.insert( self.Connections, self.GUI.CancelButton.MouseButton1Up:connect( function ()
  1749. self:cancel();
  1750. end ) );
  1751.  
  1752. table.insert( self.Connections, self.GUI.HueOption.Input.TextButton.MouseButton1Down:connect( function ()
  1753. self.GUI.HueOption.Input.TextBox:CaptureFocus();
  1754. end ) );
  1755. table.insert( self.Connections, self.GUI.HueOption.Input.TextBox.FocusLost:connect( function ( enter_pressed )
  1756. local potential_new = tonumber( self.GUI.HueOption.Input.TextBox.Text );
  1757. if potential_new then
  1758. if potential_new > 360 then
  1759. potential_new = 360;
  1760. elseif potential_new < 0 then
  1761. potential_new = 0;
  1762. end;
  1763. self:_changeColor( potential_new, self.saturation, self.value );
  1764. else
  1765. self:_updateGUI();
  1766. end;
  1767. end ) );
  1768.  
  1769. table.insert( self.Connections, self.GUI.SaturationOption.Input.TextButton.MouseButton1Down:connect( function ()
  1770. self.GUI.SaturationOption.Input.TextBox:CaptureFocus();
  1771. end ) );
  1772. table.insert( self.Connections, self.GUI.SaturationOption.Input.TextBox.FocusLost:connect( function ( enter_pressed )
  1773. local potential_new = tonumber( ( self.GUI.SaturationOption.Input.TextBox.Text:gsub( '%%', '' ) ) );
  1774. if potential_new then
  1775. if potential_new > 100 then
  1776. potential_new = 100;
  1777. elseif potential_new < 0 then
  1778. potential_new = 0;
  1779. end;
  1780. self:_changeColor( self.hue, potential_new / 100, self.value );
  1781. else
  1782. self:_updateGUI();
  1783. end;
  1784. end ) );
  1785.  
  1786. table.insert( self.Connections, self.GUI.ValueOption.Input.TextButton.MouseButton1Down:connect( function ()
  1787. self.GUI.ValueOption.Input.TextBox:CaptureFocus();
  1788. end ) );
  1789. table.insert( self.Connections, self.GUI.ValueOption.Input.TextBox.FocusLost:connect( function ( enter_pressed )
  1790. local potential_new = tonumber( ( self.GUI.ValueOption.Input.TextBox.Text:gsub( '%%', '' ) ) );
  1791. if potential_new then
  1792. if potential_new < 0 then
  1793. potential_new = 0;
  1794. elseif potential_new > 100 then
  1795. potential_new = 100;
  1796. end;
  1797. self:_changeColor( self.hue, self.saturation, potential_new / 100 );
  1798. else
  1799. self:_updateGUI();
  1800. end;
  1801. end ) );
  1802.  
  1803. end;
  1804.  
  1805. ["_onMouseMove"] = function ( self, x, y )
  1806. if not self.track_mouse then
  1807. return;
  1808. end;
  1809.  
  1810. if self.track_mouse == 'hue-saturation' then
  1811. -- Calculate the mouse position relative to the graph
  1812. local graph_x, graph_y = x - self.GUI.HueSaturation.AbsolutePosition.x, y - self.GUI.HueSaturation.AbsolutePosition.y;
  1813.  
  1814. -- Make sure we're not going out of bounds
  1815. if graph_x < 0 then
  1816. graph_x = 0;
  1817. elseif graph_x > self.GUI.HueSaturation.AbsoluteSize.x then
  1818. graph_x = self.GUI.HueSaturation.AbsoluteSize.x;
  1819. end;
  1820. if graph_y < 0 then
  1821. graph_y = 0;
  1822. elseif graph_y > self.GUI.HueSaturation.AbsoluteSize.y then
  1823. graph_y = self.GUI.HueSaturation.AbsoluteSize.y;
  1824. end;
  1825.  
  1826. -- Calculate the new color and change it
  1827. self:_changeColor( 359 * graph_x / 209, 1 - graph_y / 200, self.value );
  1828.  
  1829. elseif self.track_mouse == 'value' then
  1830. -- Calculate the mouse position relative to the value bar
  1831. local bar_y = y - self.GUI.Value.AbsolutePosition.y;
  1832.  
  1833. -- Make sure we're not going out of bounds
  1834. if bar_y < 0 then
  1835. bar_y = 0;
  1836. elseif bar_y > self.GUI.Value.AbsoluteSize.y then
  1837. bar_y = self.GUI.Value.AbsoluteSize.y;
  1838. end;
  1839.  
  1840. -- Calculate the new color and change it
  1841. self:_changeColor( self.hue, self.saturation, 1 - bar_y / 200 );
  1842. end;
  1843. end;
  1844.  
  1845. ["_changeColor"] = function ( self, hue, saturation, value )
  1846. if hue ~= hue then
  1847. hue = 359;
  1848. end;
  1849. self.hue = hue;
  1850. self.saturation = saturation == 0 and 0.01 or saturation;
  1851. self.value = value;
  1852. self:_updateGUI();
  1853. end;
  1854.  
  1855. ["_updateGUI"] = function ( self )
  1856.  
  1857. self.GUI.HueSaturation.Cursor.Position = UDim2.new( 0, 209 * self.hue / 360 - 8, 0, ( 1 - self.saturation ) * 200 - 8 );
  1858. self.GUI.Value.Cursor.Position = UDim2.new( 0, -2, 0, ( 1 - self.value ) * 200 - 8 );
  1859.  
  1860. local color = Color3.new( _HSVToRGB( self.hue, self.saturation, self.value ) );
  1861. self.GUI.ColorDisplay.BackgroundColor3 = color;
  1862. self.GUI.Value.ColorBG.BackgroundColor3 = Color3.new( _HSVToRGB( self.hue, self.saturation, 1 ) );
  1863.  
  1864. self.GUI.HueOption.Bar.BackgroundColor3 = color;
  1865. self.GUI.SaturationOption.Bar.BackgroundColor3 = color;
  1866. self.GUI.ValueOption.Bar.BackgroundColor3 = color;
  1867.  
  1868. self.GUI.HueOption.Input.TextBox.Text = math.floor( self.hue );
  1869. self.GUI.SaturationOption.Input.TextBox.Text = math.floor( self.saturation * 100 ) .. "%";
  1870. self.GUI.ValueOption.Input.TextBox.Text = math.floor( self.value * 100 ) .. "%";
  1871.  
  1872. end;
  1873.  
  1874. ["finish"] = function ( self )
  1875.  
  1876. if not self.enabled then
  1877. return;
  1878. end;
  1879.  
  1880. -- Remove the GUI
  1881. if self.GUI then
  1882. self.GUI:Destroy();
  1883. end;
  1884. self.GUI = nil;
  1885. self.track_mouse = nil;
  1886.  
  1887. -- Disconnect all temporary connections
  1888. for connection_index, connection in pairs( self.Connections ) do
  1889. connection:disconnect();
  1890. self.Connections[connection_index] = nil;
  1891. end;
  1892.  
  1893. -- Call the callback function that was provided to us
  1894. self.callback( self.hue, self.saturation, self.value );
  1895. self.callback = nil;
  1896.  
  1897. self.enabled = false;
  1898.  
  1899. end;
  1900.  
  1901. ["cancel"] = function ( self )
  1902.  
  1903. if not self.enabled then
  1904. return;
  1905. end;
  1906.  
  1907. -- Remove the GUI
  1908. if self.GUI then
  1909. self.GUI:Destroy();
  1910. end;
  1911. self.GUI = nil;
  1912. self.track_mouse = nil;
  1913.  
  1914. -- Disconnect all temporary connections
  1915. for connection_index, connection in pairs( self.Connections ) do
  1916. connection:disconnect();
  1917. self.Connections[connection_index] = nil;
  1918. end;
  1919.  
  1920. -- Call the callback function that was provided to us
  1921. self.callback();
  1922. self.callback = nil;
  1923.  
  1924. self.enabled = false;
  1925.  
  1926. end;
  1927.  
  1928. };
  1929.  
  1930. ------------------------------------------
  1931. -- Provide an interface to the
  1932. -- import/export system
  1933. ------------------------------------------
  1934. IE = {
  1935.  
  1936. ["export"] = function ()
  1937.  
  1938. if #Selection.Items == 0 then
  1939. return;
  1940. end;
  1941.  
  1942. local serialized_selection = _serializeParts( Selection.Items );
  1943.  
  1944. -- Dump to logs
  1945. -- Services.TestService:Warn( false, "[Building Tools by F3X] Exported Model: \n" .. serialized_selection );
  1946.  
  1947. -- Get ready to upload to the web for retrieval
  1948. local upload_data;
  1949. local cancelUpload;
  1950.  
  1951. -- Create the export dialog
  1952. local Dialog = Tool.Interfaces.BTExportDialog:Clone();
  1953. Dialog.Loading.Size = UDim2.new( 1, 0, 0, 0 );
  1954. Dialog.Parent = UI;
  1955. Dialog.Loading:TweenSize( UDim2.new( 1, 0, 0, 80 ), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.25 );
  1956. Dialog.Loading.CloseButton.MouseButton1Up:connect( function ()
  1957. cancelUpload();
  1958. Dialog:Destroy();
  1959. end );
  1960.  
  1961. -- Run the upload/post-upload/failure code in a coroutine
  1962. -- so it can be cancelled
  1963. coroutine.resume( coroutine.create( function ()
  1964. cancelUpload = function ()
  1965. coroutine.yield();
  1966. end;
  1967. local upload_attempt = ypcall( function ()
  1968. upload_data = PostAsync( "http://www.f3xteam.com/bt/export", serialized_selection );
  1969. end );
  1970.  
  1971. -- Make sure we're in a server
  1972. if ToolType == 'plugin' and not in_server then
  1973. Dialog.Loading.TextLabel.Text = "Use Tools > Test > Start Server to export from Studio";
  1974. Dialog.Loading.TextLabel.TextWrapped = true;
  1975. Dialog.Loading.CloseButton.Position = UDim2.new( 0, 0, 0, 50 );
  1976. Dialog.Loading.CloseButton.Text = 'Got it';
  1977. return;
  1978. end;
  1979.  
  1980. -- Fail graciously
  1981. if not upload_attempt then
  1982. Dialog.Loading.TextLabel.Text = "Upload failed";
  1983. Dialog.Loading.CloseButton.Text = 'Ok :(';
  1984. return;
  1985. end;
  1986. if not ( upload_data and type( upload_data ) == 'string' and upload_data:len() > 0 ) then
  1987. Dialog.Loading.TextLabel.Text = "Upload failed";
  1988. Dialog.Loading.CloseButton.Text = 'Ok ;(';
  1989. return;
  1990. end;
  1991. if not pcall( function () upload_data = RbxUtility.DecodeJSON( upload_data ); end ) or not upload_data then
  1992. Dialog.Loading.TextLabel.Text = "Upload failed";
  1993. Dialog.Loading.CloseButton.Text = "Ok :'(";
  1994. return;
  1995. end;
  1996. if not upload_data.success then
  1997. Dialog.Loading.TextLabel.Text = "Upload failed";
  1998. Dialog.Loading.CloseButton.Text = "Ok :''(";
  1999. end;
  2000.  
  2001. print( "[Building Tools by F3X] Uploaded Export: " .. upload_data.id );
  2002.  
  2003. Dialog.Loading.Visible = false;
  2004. Dialog.Info.Size = UDim2.new( 1, 0, 0, 0 );
  2005. Dialog.Info.CreationID.Text = upload_data.id;
  2006. Dialog.Info.Visible = true;
  2007. Dialog.Info:TweenSize( UDim2.new( 1, 0, 0, 75 ), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.25 );
  2008. Dialog.Tip.Size = UDim2.new( 1, 0, 0, 0 );
  2009. Dialog.Tip.Visible = true;
  2010. Dialog.Tip:TweenSize( UDim2.new( 1, 0, 0, 30 ), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.25 );
  2011. Dialog.Close.Size = UDim2.new( 1, 0, 0, 0 );
  2012. Dialog.Close.Visible = true;
  2013. Dialog.Close:TweenSize( UDim2.new( 1, 0, 0, 20 ), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.25 );
  2014. Dialog.Close.Button.MouseButton1Up:connect( function ()
  2015. Dialog:Destroy();
  2016. end );
  2017.  
  2018. -- Play a confirmation sound
  2019. local Sound = RbxUtility.Create "Sound" {
  2020. Name = "BTActionCompletionSound";
  2021. Pitch = 1.5;
  2022. SoundId = action_completion_sound;
  2023. Volume = 1;
  2024. Parent = Player or Services.SoundService;
  2025. };
  2026. Sound:Play();
  2027. Sound:Destroy();
  2028. end ) );
  2029.  
  2030. end;
  2031.  
  2032. };
  2033.  
  2034. ------------------------------------------
  2035. -- Prepare the dock UI
  2036. ------------------------------------------
  2037.  
  2038. Tooltips = {};
  2039.  
  2040. -- Wait for all parts of the base UI to fully replicate
  2041. if ToolType == 'tool' then
  2042. local UIComponentCount = (Tool:WaitForChild 'UIComponentCount').Value;
  2043. repeat wait( 0.1 ) until #_getAllDescendants( Tool.Interfaces ) >= UIComponentCount;
  2044. end;
  2045.  
  2046. -- Create the main GUI
  2047. Dock = Tool.Interfaces.BTDockGUI:Clone();
  2048. Dock.Parent = UI;
  2049. Dock.Visible = false;
  2050.  
  2051. -- Add functionality to each tool button
  2052. function RegisterToolButton( ToolButton )
  2053. -- Provides functionality to `ToolButton`
  2054.  
  2055. -- Get the tool name and the tool
  2056. local tool_name = ToolButton.Name:match( "(.+)Button" );
  2057.  
  2058. if tool_name then
  2059.  
  2060. -- Create the click connection
  2061. ToolButton.MouseButton1Up:connect( function ()
  2062. local Tool = Tools[tool_name];
  2063. if Tool then
  2064. equipTool( Tool );
  2065. end;
  2066. end );
  2067.  
  2068. ToolButton.MouseEnter:connect( function ()
  2069. local Tooltip = Tooltips[tool_name];
  2070. if Tooltip then
  2071. Tooltip:focus( 'button' );
  2072. end;
  2073. end );
  2074.  
  2075. ToolButton.MouseLeave:connect( function ()
  2076. local Tooltip = Tooltips[tool_name];
  2077. if Tooltip then
  2078. Tooltip:unfocus( 'button' );
  2079. end;
  2080. end );
  2081.  
  2082. end;
  2083. end;
  2084. for _, ToolButton in pairs( Dock.ToolButtons:GetChildren() ) do
  2085. RegisterToolButton( ToolButton );
  2086. end;
  2087.  
  2088. -- Prepare the tooltips
  2089. function RegisterTooltip( Tooltip )
  2090. local tool_name = Tooltip.Name:match( "(.+)Info" );
  2091.  
  2092. Tooltips[tool_name] = {
  2093.  
  2094. GUI = Tooltip;
  2095.  
  2096. button_focus = false;
  2097. tooltip_focus = false;
  2098.  
  2099. focus = function ( self, source )
  2100. if Dock.HelpInfo.Visible then
  2101. return;
  2102. end;
  2103. if source == 'button' then
  2104. self.button_focus = true;
  2105. elseif source == 'tooltip' then
  2106. self.tooltip_focus = true;
  2107. end;
  2108. for _, Tooltip in pairs( Dock.Tooltips:GetChildren() ) do
  2109. Tooltip.Visible = false;
  2110. end;
  2111. self.GUI.Visible = true;
  2112. end;
  2113.  
  2114. unfocus = function ( self, source )
  2115. if source == 'button' then
  2116. self.button_focus = false;
  2117. elseif source == 'tooltip' then
  2118. self.tooltip_focus = false;
  2119. end;
  2120. if not self.button_focus and not self.tooltip_focus then
  2121. self.GUI.Visible = false;
  2122. end;
  2123. end;
  2124.  
  2125. };
  2126.  
  2127. -- Make it disappear after it's out of mouse focus
  2128. Tooltip.MouseEnter:connect( function ()
  2129. Tooltips[tool_name]:focus( 'tooltip' );
  2130. end );
  2131. Tooltip.MouseLeave:connect( function ()
  2132. Tooltips[tool_name]:unfocus( 'tooltip' );
  2133. end );
  2134.  
  2135. -- Create the scrolling container
  2136. local ScrollingContainer = Gloo.ScrollingContainer( true, false, 15 );
  2137. ScrollingContainer.GUI.Parent = Tooltip;
  2138.  
  2139. -- Put the tooltip content in the container
  2140. for _, Child in pairs( Tooltip.Content:GetChildren() ) do
  2141. Child.Parent = ScrollingContainer.Container;
  2142. end;
  2143. ScrollingContainer.GUI.Size = Dock.Tooltips.Size;
  2144. ScrollingContainer.Container.Size = Tooltip.Content.Size;
  2145. ScrollingContainer.Boundary.Size = Dock.Tooltips.Size;
  2146. ScrollingContainer.Boundary.BackgroundTransparency = 1;
  2147. Tooltip.Content:Destroy();
  2148.  
  2149. end;
  2150. for _, Tooltip in pairs( Dock.Tooltips:GetChildren() ) do
  2151. RegisterTooltip( Tooltip );
  2152. end;
  2153.  
  2154. -- Create the scrolling container for the help tooltip
  2155. local ScrollingContainer = Gloo.ScrollingContainer( true, false, 15 );
  2156. ScrollingContainer.GUI.Parent = Dock.HelpInfo;
  2157.  
  2158. -- Put the help tooltip content in the container
  2159. for _, Child in pairs( Dock.HelpInfo.Content:GetChildren() ) do
  2160. Child.Parent = ScrollingContainer.Container;
  2161. end;
  2162. ScrollingContainer.GUI.Size = Dock.HelpInfo.Size;
  2163. ScrollingContainer.Container.Size = Dock.HelpInfo.Content.Size;
  2164. ScrollingContainer.Boundary.Size = Dock.HelpInfo.Size;
  2165. ScrollingContainer.Boundary.BackgroundTransparency = 1;
  2166. Dock.HelpInfo.Content:Destroy();
  2167.  
  2168. -- Add functionality to the other GUI buttons
  2169. Dock.SelectionButtons.UndoButton.MouseButton1Up:connect( function ()
  2170. History:undo();
  2171. end );
  2172. Dock.SelectionButtons.RedoButton.MouseButton1Up:connect( function ()
  2173. History:redo();
  2174. end );
  2175. Dock.SelectionButtons.DeleteButton.MouseButton1Up:connect( function ()
  2176. deleteSelection();
  2177. end );
  2178. Dock.SelectionButtons.CloneButton.MouseButton1Up:connect( function ()
  2179. cloneSelection();
  2180. end );
  2181. Dock.SelectionButtons.ExportButton.MouseButton1Up:connect( function ()
  2182. IE:export();
  2183. end );
  2184. Dock.InfoButtons.HelpButton.MouseButton1Up:connect( function ()
  2185. toggleHelp();
  2186. end );
  2187.  
  2188. -- Shade the buttons according to whether they'll function or not
  2189. Selection.Changed:connect( function ()
  2190.  
  2191. -- If there are items, they should be active
  2192. if #Selection.Items > 0 then
  2193. Dock.SelectionButtons.DeleteButton.Image = delete_active_decal;
  2194. Dock.SelectionButtons.CloneButton.Image = clone_active_decal;
  2195. Dock.SelectionButtons.ExportButton.Image = export_active_decal;
  2196.  
  2197. -- If there aren't items, they shouldn't be active
  2198. else
  2199. Dock.SelectionButtons.DeleteButton.Image = delete_inactive_decal;
  2200. Dock.SelectionButtons.CloneButton.Image = clone_inactive_decal;
  2201. Dock.SelectionButtons.ExportButton.Image = export_inactive_decal;
  2202. end;
  2203.  
  2204. end );
  2205.  
  2206. -- Make the selection/info buttons display tooltips upon hovering over them
  2207. for _, SelectionButton in pairs( Dock.SelectionButtons:GetChildren() ) do
  2208. SelectionButton.MouseEnter:connect( function ()
  2209. if SelectionButton:FindFirstChild( 'Tooltip' ) then
  2210. SelectionButton.Tooltip.Visible = true;
  2211. end;
  2212. end );
  2213. SelectionButton.MouseLeave:connect( function ()
  2214. if SelectionButton:FindFirstChild( 'Tooltip' ) then
  2215. SelectionButton.Tooltip.Visible = false;
  2216. end;
  2217. end );
  2218. end;
  2219. Dock.InfoButtons.HelpButton.MouseEnter:connect( function ()
  2220. Dock.InfoButtons.HelpButton.Tooltip.Visible = true;
  2221. end );
  2222. Dock.InfoButtons.HelpButton.MouseLeave:connect( function ()
  2223. Dock.InfoButtons.HelpButton.Tooltip.Visible = false;
  2224. end );
  2225.  
  2226. History.Changed:connect( function ()
  2227.  
  2228. -- If there are any records
  2229. if #History.Data > 0 then
  2230.  
  2231. -- If we're at the beginning
  2232. if History.index == 0 then
  2233. Dock.SelectionButtons.UndoButton.Image = undo_inactive_decal;
  2234. Dock.SelectionButtons.RedoButton.Image = redo_active_decal;
  2235.  
  2236. -- If we're at the end
  2237. elseif History.index == #History.Data then
  2238. Dock.SelectionButtons.UndoButton.Image = undo_active_decal;
  2239. Dock.SelectionButtons.RedoButton.Image = redo_inactive_decal;
  2240.  
  2241. -- If we're neither at the beginning or the end
  2242. else
  2243. Dock.SelectionButtons.UndoButton.Image = undo_active_decal;
  2244. Dock.SelectionButtons.RedoButton.Image = redo_active_decal;
  2245. end;
  2246.  
  2247. -- If there are no records
  2248. else
  2249. Dock.SelectionButtons.UndoButton.Image = undo_inactive_decal;
  2250. Dock.SelectionButtons.RedoButton.Image = redo_inactive_decal;
  2251. end;
  2252.  
  2253. end );
  2254.  
  2255. ------------------------------------------
  2256. -- Attach tool event listeners
  2257. ------------------------------------------
  2258.  
  2259. function equipBT( CurrentMouse )
  2260.  
  2261. Mouse = CurrentMouse;
  2262.  
  2263. -- Enable the move tool if there's no tool currently enabled
  2264. if not CurrentTool then
  2265. equipTool( Tools.Move );
  2266. end;
  2267.  
  2268. if not TargetBox then
  2269. TargetBox = Instance.new( "SelectionBox", UI );
  2270. TargetBox.Name = "BTTargetBox";
  2271. TargetBox.Color = BrickColor.new( "Institutional white" );
  2272. TargetBox.Transparency = 0.5;
  2273. end;
  2274.  
  2275. -- Enable any temporarily-disabled selection boxes
  2276. for _, SelectionBox in pairs( SelectionBoxes ) do
  2277. SelectionBox.Parent = UI;
  2278. end;
  2279.  
  2280. -- Update the internal selection if this is a plugin
  2281. if ToolType == 'plugin' then
  2282. for _, Item in pairs( Services.Selection:Get() ) do
  2283. Selection:add( Item );
  2284. end;
  2285. end;
  2286.  
  2287. -- Call the `Equipped` listener of the current tool
  2288. if CurrentTool and CurrentTool.Listeners.Equipped then
  2289. CurrentTool.Listeners.Equipped();
  2290. end;
  2291.  
  2292. -- Show the dock
  2293. Dock.Visible = true;
  2294.  
  2295. -- Display update notification if any (for the tool version)
  2296. if ToolType == 'tool' then
  2297. coroutine.wrap( ShowUpdateNotification )();
  2298. end;
  2299.  
  2300. table.insert( Connections, Mouse.KeyDown:connect( function ( key )
  2301.  
  2302. local key = key:lower();
  2303. local key_code = key:byte();
  2304.  
  2305. -- Provide the abiltiy to delete via the shift + X key combination
  2306. if ActiveKeys[47] or ActiveKeys[48] and key == "x" then
  2307. deleteSelection();
  2308. return;
  2309. end;
  2310.  
  2311. -- Provide the ability to clone via the shift + C key combination
  2312. if ActiveKeys[47] or ActiveKeys[48] and key == "c" then
  2313. cloneSelection();
  2314. return;
  2315. end;
  2316.  
  2317. -- Undo if shift+z is pressed
  2318. if key == "z" and ( ActiveKeys[47] or ActiveKeys[48] ) then
  2319. History:undo();
  2320. return;
  2321.  
  2322. -- Redo if shift+y is pressed
  2323. elseif key == "y" and ( ActiveKeys[47] or ActiveKeys[48] ) then
  2324. History:redo();
  2325. return;
  2326. end;
  2327.  
  2328. -- Serialize and dump selection to logs if shift+p is pressed
  2329. if key == "p" and ( ActiveKeys[47] or ActiveKeys[48] ) then
  2330. IE:export();
  2331. return;
  2332. end;
  2333.  
  2334. -- Perform a prism selection if shift + k is pressed
  2335. if key == "k" and ( ActiveKeys[47] or ActiveKeys[48] ) then
  2336. prismSelect();
  2337. return;
  2338. end;
  2339.  
  2340. -- Clear the selection if shift + r is pressed
  2341. if key == "r" and ( ActiveKeys[47] or ActiveKeys[48] ) then
  2342. Selection:clear();
  2343. return;
  2344. end;
  2345.  
  2346. if key == "z" then
  2347. equipTool( Tools.Move );
  2348.  
  2349. elseif key == "x" then
  2350. equipTool( Tools.Resize );
  2351.  
  2352. elseif key == "c" then
  2353. equipTool( Tools.Rotate );
  2354.  
  2355. elseif key == "v" then
  2356. equipTool( Tools.Paint );
  2357.  
  2358. elseif key == "b" then
  2359. equipTool( Tools.Surface );
  2360.  
  2361. elseif key == "n" then
  2362. equipTool( Tools.Material );
  2363.  
  2364. elseif key == "m" then
  2365. equipTool( Tools.Anchor );
  2366.  
  2367. elseif key == "k" then
  2368. equipTool( Tools.Collision );
  2369.  
  2370. elseif key == "j" then
  2371. equipTool( Tools.NewPart );
  2372.  
  2373. elseif key == "h" then
  2374. equipTool( Tools.Mesh );
  2375.  
  2376. elseif key == "g" then
  2377. equipTool( Tools.Texture );
  2378.  
  2379. elseif key == "f" then
  2380. equipTool( Tools.Weld );
  2381.  
  2382. elseif key == "u" then
  2383. equipTool( Tools.Lighting );
  2384.  
  2385. elseif key == "p" then
  2386. equipTool( Tools.Decorate );
  2387.  
  2388. end;
  2389.  
  2390. ActiveKeys[key_code] = key_code;
  2391. ActiveKeys[key] = key;
  2392.  
  2393. -- If it's now in multiselection mode, update `selecting`
  2394. -- (these are the left/right ctrl & shift keys)
  2395. if ActiveKeys[47] or ActiveKeys[48] or ActiveKeys[49] or ActiveKeys[50] then
  2396. selecting = ActiveKeys[47] or ActiveKeys[48] or ActiveKeys[49] or ActiveKeys[50];
  2397. end;
  2398.  
  2399. end ) );
  2400.  
  2401. table.insert( Connections, Mouse.KeyUp:connect( function ( key )
  2402.  
  2403. local key = key:lower();
  2404. local key_code = key:byte();
  2405.  
  2406. ActiveKeys[key_code] = nil;
  2407. ActiveKeys[key] = nil;
  2408.  
  2409. -- If it's no longer in multiselection mode, update `selecting` & related values
  2410. if selecting and not ActiveKeys[selecting] then
  2411. selecting = false;
  2412. if Select2D.enabled then
  2413. Select2D:select();
  2414. Select2D:finish();
  2415. end;
  2416. end;
  2417.  
  2418. -- Fire tool listeners
  2419. if CurrentTool and CurrentTool.Listeners.KeyUp then
  2420. CurrentTool.Listeners.KeyUp( key );
  2421. end;
  2422.  
  2423. end ) );
  2424.  
  2425. table.insert( Connections, Mouse.Button1Down:connect( function ()
  2426.  
  2427. clicking = true;
  2428. click_x, click_y = Mouse.X, Mouse.Y;
  2429.  
  2430. -- If multiselection is, just add to the selection
  2431. if selecting then
  2432. return;
  2433. end;
  2434.  
  2435. -- Fire tool listeners
  2436. if CurrentTool and CurrentTool.Listeners.Button1Down then
  2437. CurrentTool.Listeners.Button1Down();
  2438. end;
  2439.  
  2440. end ) );
  2441.  
  2442. table.insert( Connections, Mouse.Move:connect( function ()
  2443.  
  2444. -- If the mouse has moved since it was clicked, start 2D selection mode
  2445. if not override_selection and not Select2D.enabled and clicking and selecting and ( click_x ~= Mouse.X or click_y ~= Mouse.Y ) then
  2446. Select2D:start();
  2447. end;
  2448.  
  2449. -- If the target has changed, update the selectionbox appropriately
  2450. if not override_selection and isSelectable( Mouse.Target ) and TargetBox.Adornee ~= Mouse.Target then
  2451. TargetBox.Adornee = Mouse.Target;
  2452. end;
  2453.  
  2454. -- When aiming at something invalid, don't highlight any targets
  2455. if not override_selection and not isSelectable( Mouse.Target ) then
  2456. TargetBox.Adornee = nil;
  2457. end;
  2458.  
  2459. -- Fire tool listeners
  2460. if CurrentTool and CurrentTool.Listeners.Move then
  2461. CurrentTool.Listeners.Move();
  2462. end;
  2463.  
  2464. if override_selection then
  2465. override_selection = false;
  2466. end;
  2467.  
  2468. end ) );
  2469.  
  2470. table.insert( Connections, Mouse.Button1Up:connect( function ()
  2471.  
  2472. clicking = false;
  2473.  
  2474. -- Make sure the person didn't accidentally miss a handle or something
  2475. if not Select2D.enabled and ( Mouse.X ~= click_x or Mouse.Y ~= click_y ) then
  2476. override_selection = true;
  2477. end;
  2478.  
  2479. -- If the target when clicking was invalid then clear the selection (unless we're multi-selecting)
  2480. if not override_selection and not selecting and not isSelectable( Mouse.Target ) then
  2481. Selection:clear();
  2482. end;
  2483.  
  2484. -- If multi-selecting, add to/remove from the selection
  2485. if not override_selection and selecting then
  2486.  
  2487. -- If the item isn't already selected, add it to the selection
  2488. if not Selection:find( Mouse.Target ) then
  2489. if isSelectable( Mouse.Target ) then
  2490. Selection:add( Mouse.Target );
  2491. end;
  2492.  
  2493. -- If the item _is_ already selected, remove it from the selection
  2494. else
  2495. if ( Mouse.X == click_x and Mouse.Y == click_y ) and Selection:find( Mouse.Target ) then
  2496. Selection:remove( Mouse.Target );
  2497. end;
  2498. end;
  2499.  
  2500. -- If not multi-selecting, replace the selection
  2501. else
  2502. if not override_selection and isSelectable( Mouse.Target ) then
  2503. Selection:clear();
  2504. Selection:add( Mouse.Target );
  2505. end;
  2506. end;
  2507.  
  2508. -- Fire tool listeners
  2509. if CurrentTool and CurrentTool.Listeners.Button1Up then
  2510. CurrentTool.Listeners.Button1Up();
  2511. end;
  2512.  
  2513. if override_selection then
  2514. override_selection = false;
  2515. end;
  2516.  
  2517. end ) );
  2518.  
  2519. table.insert( Connections, Mouse.Button2Down:connect( function ()
  2520. -- Fire tool listeners
  2521. if CurrentTool and CurrentTool.Listeners.Button2Down then
  2522. CurrentTool.Listeners.Button2Down();
  2523. end;
  2524. end ) );
  2525.  
  2526. table.insert( Connections, Mouse.Button2Up:connect( function ()
  2527. -- Fire tool listeners
  2528. if CurrentTool and CurrentTool.Listeners.Button2Up then
  2529. CurrentTool.Listeners.Button2Up();
  2530. end;
  2531. end ) );
  2532.  
  2533. end;
  2534.  
  2535. function unequipBT()
  2536.  
  2537. Mouse = nil;
  2538.  
  2539. -- Remove the mouse target SelectionBox from `Player`
  2540. if TargetBox then
  2541. TargetBox:Destroy();
  2542. TargetBox = nil;
  2543. end;
  2544.  
  2545. -- Disable all the selection boxes temporarily
  2546. for _, SelectionBox in pairs( SelectionBoxes ) do
  2547. SelectionBox.Parent = nil;
  2548. end;
  2549.  
  2550. -- Hide the dock
  2551. Dock.Visible = false;
  2552.  
  2553. -- Disconnect temporary platform-related connections
  2554. for connection_index, Connection in pairs( Connections ) do
  2555. Connection:disconnect();
  2556. Connections[connection_index] = nil;
  2557. end;
  2558.  
  2559. -- Call the `Unequipped` listener of the current tool
  2560. if CurrentTool and CurrentTool.Listeners.Unequipped then
  2561. CurrentTool.Listeners.Unequipped();
  2562. end;
  2563.  
  2564. end;
  2565.  
  2566.  
  2567. ------------------------------------------
  2568. -- Provide the platform's environment for
  2569. -- other tool scripts to extend upon
  2570. ------------------------------------------
  2571.  
  2572. local tool_list = {
  2573. "Anchor",
  2574. "Collision",
  2575. "Material",
  2576. "Mesh",
  2577. "Move",
  2578. "NewPart",
  2579. "Paint",
  2580. "Resize",
  2581. "Rotate",
  2582. "Surface",
  2583. "Texture",
  2584. "Weld",
  2585. "Lighting",
  2586. "Decorate"
  2587. };
  2588.  
  2589. -- Make sure all the tool scripts are in the tool & deactivate them
  2590. for _, tool_name in pairs( tool_list ) do
  2591. local script_name = "BT" .. tool_name .. "Tool";
  2592. repeat wait() until script:FindFirstChild( script_name );
  2593. script[script_name].Disabled = true;
  2594. end;
  2595.  
  2596. -- Load the platform
  2597. if not _G.BTCoreEnv then
  2598. _G.BTCoreEnv = {};
  2599. end;
  2600. _G.BTCoreEnv[Tool] = getfenv( 0 );
  2601. CoreReady = true;
  2602.  
  2603. -- Reload the tool scripts
  2604. for _, tool_name in pairs( tool_list ) do
  2605. local script_name = "BT" .. tool_name .. "Tool";
  2606. script[script_name].Disabled = false;
  2607. end;
  2608.  
  2609. -- Wait for all the tools to load
  2610. for _, tool_name in pairs( tool_list ) do
  2611. if not Tools[tool_name] then
  2612. repeat wait() until Tools[tool_name];
  2613. end;
  2614. repeat wait() until Tools[tool_name].Loaded;
  2615. end;
  2616.  
  2617. -- Activate the plugin and tool connections
  2618. if ToolType == 'plugin' then
  2619. local ToolbarButton = plugin:CreateToolbar( 'Building Tools by F3X' ):CreateButton( '', 'Building Tools by F3X', plugin_icon );
  2620. local plugin_active = false;
  2621. ToolbarButton.Click:connect( function ()
  2622. if plugin_active then
  2623. plugin_active = false;
  2624. unequipBT();
  2625. else
  2626. plugin_active = true;
  2627. plugin:Activate( true );
  2628. equipBT( plugin:GetMouse() );
  2629. end;
  2630. end );
  2631. plugin.Deactivation:connect( unequipBT );
  2632.  
  2633. elseif ToolType == 'tool' then
  2634. Tool.Equipped:connect( equipBT );
  2635. Tool.Unequipped:connect( unequipBT );
  2636. end;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement