null
nil
-
true
true
0
0
-0.699999988
-0
0
1
1
-0
0
0
1
0
RotateTool
http://www.roblox.com/asset/?id=59103214
Rotate
-
false
-0.5
0.5
0
0
-0.5
0.5
0
0
199
-35
1.60000002
3.5
0
0
-1
0
1
0
1
0
0
true
0.5
0.300000012
-0.5
0.5
0
0
-0.5
0.5
0
0
true
256
Handle
0
-0.5
0.5
0
0
0
0
0
-0.5
0.5
0
0
0
0
0
0
1
1
1
0.800000012
2
-
2
2
http://www.roblox.com/asset/?id=16884681
5
Mesh
0
0
0
0.600000024
0.600000024
0.600000024
http://www.roblox.com/asset/?id=16884673
1
1
1
-
false
RotateScript
-- NOTES:
-- NEEDS:
-- X 1.) Make single blocks rotate always (they rotate 2x, then seem to think of themselves as blockers of themselves... whaaa?)
-- X 2.) Make scripts continue to work through rotations (or else error on any objects which can't be rotated... prehaps elevators?)
-- 3.) File bug for #1 and for "# of parts counted BEFORE instance-filter is applied for game.Workspace:FindPartsInRegion3"
-- 4.) Make things rotate separately (break and make welds)
-- 5.) When something else is attached to a single block, stuff still breaks X<
-- general functions
function waitForProperty(instance, name)
while not instance[name] do
instance.Changed:wait()
end
end
function waitForChild(instance, name)
while not instance:FindFirstChild(name) do
instance.ChildAdded:wait()
end
end
local Tool = script.Parent
local player
local playerArea = nil
local selectionBox = nil
local selectedModel = nil
local terrainSelectionBox = Instance.new("Part")
terrainSelectionBox.Parent = nil
terrainSelectionBox.formFactor = "Custom"
terrainSelectionBox.Size = Vector3.new(4, 4, 4)
terrainSelectionBox.CFrame = CFrame.new()
local cluster = nil
waitForChild(Tool, "ErrorBox")
local errorBox = Tool.ErrorBox
waitForChild(Tool, "PlayerOwner")
local playerOwner = Tool.PlayerOwner
waitForProperty(game,"PlaceId")
local isRestricted = (game.PlaceId == 41324860 or game.PlaceId == 129686177)
game:GetService("ContentProvider"):Preload("rbxasset://icons/configure_sel.png")
function getBoundingBox2(partOrModel)
-- for models, the bounding box is defined as the minimum and maximum individual part bounding boxes
-- relative to the first part's coordinate frame.
local minVec = Vector3.new(math.huge, math.huge, math.huge)
local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
if partOrModel:IsA("Part") or partOrModel:IsA("WedgePart") or partOrModel:IsA("TrussPart")then
minVec = partOrModel.CFrame:pointToWorldSpace(-0.5 * partOrModel.Size)
maxVec = partOrModel.CFrame:pointToWorldSpace(0.5 * partOrModel.Size)
else
local part1 = partOrModel:GetChildren()[1]
for i, object in pairs(partOrModel:GetChildren()) do
if (object:IsA("Part") or object:IsA("WedgePart") or object:IsA("TrussPart")) then
boxMinInWorld1 = object.CFrame:pointToWorldSpace(-0.5 * object.Size)
--boxMinInPart1 = part1.CFrame:pointToObjectSpace(boxMinInWorld)
boxMaxInWorld1 = object.CFrame:pointToWorldSpace(0.5 * object.Size)
--boxMaxInPart1 = part1.CFrame:pointToObjectSpace(boxMaxInWorld)
local minX = minVec.x
local minY = minVec.y
local minZ = minVec.z
local maxX = maxVec.x
local maxY = maxVec.y
local maxZ = maxVec.z
if boxMinInWorld1.x < minVec.x then
minX = boxMinInWorld1.x
end
if boxMinInWorld1.y < minVec.y then
minY = boxMinInWorld1.y
end
if boxMinInWorld1.z < minVec.z then
minZ = boxMinInWorld1.z
end
if boxMaxInWorld1.x < minX then
minX = boxMaxInWorld1.x
end
if boxMaxInWorld1.y < minY then
minY = boxMaxInWorld1.y
end
if boxMaxInWorld1.z < minZ then
minZ = boxMaxInWorld1.z
end
if boxMinInWorld1.x > maxVec.x then
maxX = boxMinInWorld1.x
end
if boxMinInWorld1.y > maxVec.y then
maxY = boxMinInWorld1.y
end
if boxMinInWorld1.z > maxVec.z then
maxZ = boxMinInWorld1.z
end
if boxMaxInWorld1.x > maxX then
maxX = boxMaxInWorld1.x
end
if boxMaxInWorld1.y > maxY then
maxY = boxMaxInWorld1.y
end
if boxMaxInWorld1.z > maxZ then
maxZ = boxMaxInWorld1.z
end
minVec = Vector3.new(minX, minY, minZ)
maxVec = Vector3.new(maxX, maxY, maxZ)
end
end
end
return minVec, maxVec
end
function isInRobloxModel(part)
if part == game.Workspace then
return false, nil
elseif part:FindFirstChild("RobloxModel") then
return true, part
else
return isInRobloxModel(part.Parent)
end
end
function isInMyArea(part)
if playerArea:FindFirstChild("PlayerArea")then
return playerArea.PlayerArea:IsAncestorOf(part)
end
return false
end
function on3dButton1Down(mouse)
-- don't do anything for now (can fix later: for now this means you can click one model, drag mouse to second model, and release mouse, and this will activate second model)
end
function offsetPartsByVector3(partOrModel, offsetVector)
local insertCFrame
if partOrModel:IsA("Model") then
for i, object in pairs(partOrModel:GetChildren()) do
if (object:IsA("Part") or object:IsA("WedgePart") or object:IsA("TrussPart")) then
object.CFrame = object.CFrame + offsetVector
end
end
else
partOrModel.CFrame = partOrModel.CFrame + offsetVector
end
end
function storeAndDisableScriptsInModel(parent, scriptTable)
for i, object in pairs(parent:GetChildren()) do
if object:IsA("Script") or object:IsA("LocalScript") then if not object.Disabled then object.Disabled = true table.insert(scriptTable, object) end end
if object.GetChildren then storeAndDisableScriptsInModel(object, scriptTable) end
end
end
function isInternalWeld(weld, model)
return (not weld.Part0 or weld.Part0:IsDescendantOf(model)) and (not weld.Part1 or weld.Part1:IsDescendantOf(model))
end
function storeAndRemoveWeldsInModel(initialmodel, model, welds, weldParents)
for i, object in pairs(model:GetChildren()) do
if object.className == "ManualWeld" then if isInternalWeld(object, initialmodel) then table.insert(welds, object) table.insert(weldParents, object.Parent) object.Parent = nil end end
if object.GetChildren then storeAndRemoveWeldsInModel(initialmodel, object, welds, weldParents) end
end
end
local debris = game:GetService("Debris")
function flashRedBox(modelToFlash)
if not modelToFlash then return end
errorBox.Parent = player.PlayerGui
errorBox.Adornee = modelToFlash
delay(0,function()
for i = 1, 3 do
errorBox.Visible = true
wait(0.13)
errorBox.Visible = false
wait(0.13)
end
errorBox.Adornee = nil
errorBox.Parent = Tool
end)
end
-- below function should work as a Region3 query, returning true if a single cluster part is within this region
function clusterPartsInRegion(startVector, endVector)
if not cluster then return false end
local startCell = cluster:WorldToCell(startVector)
local endCell = cluster:WorldToCell(endVector)
local startX = startCell.X
local startY = startCell.Y
local startZ = startCell.Z
local endX = endCell.X
local endY = endCell.Y
local endZ = endCell.Z
if startX < cluster.MaxExtents.Min.X then startX = cluster.MaxExtents.Min.X end
if startY < cluster.MaxExtents.Min.Y then startY = cluster.MaxExtents.Min.Y end
if startZ < cluster.MaxExtents.Min.Z then startZ = cluster.MaxExtents.Min.Z end
if endX > cluster.MaxExtents.Max.X then endX = cluster.MaxExtents.Max.X end
if endY > cluster.MaxExtents.Max.Y then endY = cluster.MaxExtents.Max.Y end
if endZ > cluster.MaxExtents.Max.Z then endZ = cluster.MaxExtents.Max.Z end
for x = startX, endX do
for y = startY, endY do
for z = startZ, endZ do
if (cluster:GetCell(x, y, z).Value) > 0 then return true end
end
end
end
return false
end
function waterDirectionRotated(waterDirection)
if waterDirection == Enum.WaterDirection.NegX then return Enum.WaterDirection.NegZ end
if waterDirection == Enum.WaterDirection.NegZ then return Enum.WaterDirection.X end
if waterDirection == Enum.WaterDirection.X then return Enum.WaterDirection.Z end
if waterDirection == Enum.WaterDirection.Z then return Enum.WaterDirection.NegX end
return waterDirection
end
local debounce = false
function on3dButton1Up(mouse)
local modelToRotate = selectedModel -- so that other mouse events can't give us race conditions
if modelToRotate and not debounce then
debounce = true
if modelToRotate == terrainSelectionBox then
-- just rotate the terrain cell if we're selecting on terrain
local cellPos = game.Workspace.Terrain:WorldToCell(terrainSelectionBox.CFrame.p)
local cellMat, cellType, cellOrient = game.Workspace.Terrain:GetCell(cellPos.X, cellPos.Y, cellPos.Z)
local isWater, waterForce, waterDirection = game.Workspace.Terrain:GetWaterCell(cellPos.X, cellPos.Y, cellPos.Z)
if isWater then
game.Workspace.Terrain:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, waterForce, waterDirectionRotated(waterDirection))
else
game.Workspace.Terrain:SetCell(cellPos.X, cellPos.Y, cellPos.Z, cellMat, cellType, (cellOrient.Value + 1)%4)
end
debounce = false
return
end
-- get the model centroid
local minBB, maxBB = getBoundingBox2(modelToRotate)
local oldModelCentroid = (minBB + maxBB) / 2 -- point to rotate around
local diagVector = minBB - oldModelCentroid
local rotatedDiagVector = Vector3.new(diagVector.Z, diagVector.Y, diagVector.X)
local rotatedMinBB = oldModelCentroid + rotatedDiagVector
local rotatedMaxBB = oldModelCentroid - rotatedDiagVector
-- check if part rotation will cause collision
local fudgeVector = Vector3.new(0.4, 0.4, 0.4) -- mmmmmm, fudge
-- we need to check the even/odd parity on the x and z axes of the model. If there is a difference, then the rotation will push the model off-grid, so we will
-- need to adjust
local adjustVector = Vector3.new(0, 0, 0)
local diffVector = minBB - maxBB
local garbage, xParity = math.modf(math.modf(math.abs(diffVector.X)/4 + .5)/2)
local garbage, zParity = math.modf(math.modf(math.abs(diffVector.Z)/4 + .5)/2)
xParity = math.floor(xParity*2 + .5)
zParity = math.floor(zParity*2 + .5)
if xParity ~= zParity then
-- need to shift
adjustVector = Vector3.new(2, 0, 2)
local mouseHitFrame = mouse.Hit
if mouseHitFrame then
local mouseHit = mouseHitFrame.p
if math.abs(diffVector.X) > math.abs(diffVector.Z) then
-- longest axis is X-axis
if mouseHit.X > oldModelCentroid.X then
adjustVector = Vector3.new(2, 0, 2)
else
adjustVector = Vector3.new(-2, 0, -2)
end
else
-- longest axis is Z-axis
if mouseHit.Z > oldModelCentroid.Z then
adjustVector = Vector3.new(-2, 0, 2)
else
adjustVector = Vector3.new(2, 0, -2)
end
end
end
end
-- below line checks CURRENT BB, not post-rotation BB
--local blockingParts = game.Workspace:FindPartsInRegion3(Region3.new(minBB + fudgeVector, maxBB - fudgeVector), modelToRotate, 100)
-- if blocked by the cluster, then exit out
if cluster and clusterPartsInRegion(minBB + fudgeVector, maxBB - fudgeVector) then
debounce = false
flashRedBox(modelToRotate)
return
end
local blockingParts = game.Workspace:FindPartsInRegion3(Region3.new(rotatedMinBB + fudgeVector + adjustVector, rotatedMaxBB - fudgeVector + adjustVector), modelToRotate, 100)
if #blockingParts > 1 or (#blockingParts > 0 and blockingParts[1] ~= modelToRotate) then
-- BLOCKED!! MAKE ERROR NOISE!
for j = 1, #blockingParts do
if blockingParts[j].className ~= "WedgePart" and (blockingParts[j].Size / 2):Dot(blockingParts[j].Size / 2) > 9 and blockingParts[j] ~= modelToRotate then
debounce = false
flashRedBox(modelToRotate)
return
end
end
--else
end
-- do the rotation! :D
local rotCF = CFrame.fromEulerAnglesXYZ(0, math.pi/2, 0)
game.JointsService:SetJoinAfterMoveInstance(modelToRotate)
game.JointsService:ClearJoinAfterMoveJoints()
-- below simple script disabling/re-enabling works for all scripts in normal usabilityset except for elevators and retracting spike [see if just need to change "Weld" in spikescript to "ManualWeld"... may need to also make sure below script-table can store all descendent scripts of modelToRotate, and not just immediate children...]
-- and elevator scripts only break if you rotate when the elevator is in the "fully down" position... probably just need some sort of check in ElevatorScript for this case
local scriptsToTurnBackOn = {}
storeAndDisableScriptsInModel(modelToRotate, scriptsToTurnBackOn)
local weldsToReturn = {}
local weldParentsToReturn = {}
storeAndRemoveWeldsInModel(modelToRotate, modelToRotate, weldsToReturn, weldParentsToReturn)
modelToRotate:BreakJoints()
if modelToRotate:IsA("Model") then
for i, object in pairs(modelToRotate:GetChildren()) do
if object:IsA("Part") or object:IsA("TrussPart") or object:IsA("WedgePart") then object.CFrame = rotCF * object.CFrame end
end
else
modelToRotate.CFrame = rotCF * modelToRotate.CFrame
end
-- fix position so centroid remains in same place [and then move centroid by adjustVector so it stays on grid]
local newMinBB, newMaxBB = getBoundingBox2(modelToRotate)
local newModelCentroid = (newMinBB + newMaxBB) / 2
offsetPartsByVector3(modelToRotate, oldModelCentroid - newModelCentroid + adjustVector)
game.JointsService:CreateJoinAfterMoveJoints()
modelToRotate:MakeJoints()
-- return all manual welds
for i = 1, #weldsToReturn do weldsToReturn[i].Parent = weldParentsToReturn[i] end
-- turn back on scripts
for i = 1, #scriptsToTurnBackOn do scriptsToTurnBackOn[i].Disabled = false end
--end
--[[ for debugging
local tempPart = Instance.new("Part")
tempPart.CanCollide = false
tempPart.Anchored = true
tempPart.Size = maxBB - minBB
tempPart.CFrame = CFrame.new((minBB + maxBB)/2)
tempPart.Parent = game.Workspace
game:GetService("Debris"):AddItem(tempPart, .5) ]]--
debounce = false
end
end
function on3dMouseMove(mouse)
local mouseModel
if mouse.Target and mouse.Target:IsA("Terrain") and not isRestricted and mouse.Hit and (mouse.Hit.p - Tool.Parent.Head.Position).magnitude < 60 then
-- do something special if we're selecting close-by terrain
selectedModel = terrainSelectionBox
local cell = game.Workspace.Terrain:WorldToCellPreferSolid(mouse.Hit.p)
local cellCenter = game.Workspace.Terrain:CellCenterToWorld(cell.X, cell.Y, cell.Z)
terrainSelectionBox.CFrame = CFrame.new(cellCenter)
selectionBox.Adornee = selectedModel
return
end
if mouse.Target == nil then mouseModel = nil
else boolGarbage, mouseModel = isInRobloxModel(mouse.Target) end
if mouseModel == nil or (isRestricted and (not isInMyArea(mouseModel))) then mouseModel = nil end
-- see if need to switch selectionBox
if mouseModel ~= selectedModel then
selectedModel = mouseModel
selectionBox.Adornee = selectedModel
end
end
function canSelectObject(part)
if isRestricted then
waitForChild(playerArea,"PlayerArea")
if playerArea:FindFirstChild("PlayerArea") and part:IsDescendantOf(playerArea.PlayerArea) then
return part and not (part.Locked) and part:IsA("BasePart")
else
return false
end
end
return part and not (part.Locked) and part:IsA("BasePart")
end
function onEquippedLocal(mouse)
if game.Workspace:FindFirstChild("Terrain") then
cluster = game.Workspace.Terrain
end
local character = script.Parent.Parent
player = game.Players:GetPlayerFromCharacter(character)
if player == nil then return end
if playerOwner.Value and playerOwner.Value ~= player then return end
playerOwner.Value = player
if isRestricted then
waitForChild(game.Workspace,"BuildingAreas")
waitForChild(game.Workspace.BuildingAreas,"Area1")
waitForChild(game.Workspace.BuildingAreas.Area1,"Player")
local areas = game.Workspace.BuildingAreas:GetChildren()
for i = 1, #areas do
if areas[i]:FindFirstChild("Player") and areas[i].Player.Value == player.Name then
playerArea = areas[i]
break
end
end
end
if game.Workspace:FindFirstChild("BaseplateBumpers") then mouse.TargetFilter = game.Workspace.BaseplateBumpers end
mouse.Icon = "http://www.roblox.com/asset?id=67163124"
mouse.Button1Down:connect(function() on3dButton1Down(mouse) end)
mouse.Button1Up:connect(function() on3dButton1Up(mouse) end)
mouse.Move:connect(function() on3dMouseMove(mouse) end)
selectionBox = Instance.new("SelectionBox")
selectionBox.Name = "MainSelectionBox"
selectionBox.Color = BrickColor.Blue()
selectionBox.Adornee = nil
selectionBox.Parent = player.PlayerGui;
on3dMouseMove(mouse) -- so if they unequip/reequip, they still have selection box
end
function onUnequippedLocal()
if selectionBox then selectionBox:Remove() end
selectedModel = nil
player = nil
end
Tool.Equipped:connect(onEquippedLocal)
Tool.Unequipped:connect(onUnequippedLocal)
-
[null]
21
ErrorBox
0
false
-
PlayerOwner
[null]