2013/luau/73157242.luau

3003 lines
82 KiB
Plaintext

-- RbxStamper
print "[Mercury]: Loaded corescript 73157242"
local ChangeHistoryService = game:GetService "ChangeHistoryService"
local t = {}
-- function waitForChild(instance, name)
-- while not instance:FindFirstChild(name) do
-- instance.ChildAdded:wait()
-- end
-- end
-- Do a line/plane intersection. The line starts at the camera. The plane is at y == 0, normal(0, 1, 0)
--
-- vectorPos - End point of the line.
--
-- Return:
-- cellPos - The terrain cell intersection point if there is one, vectorPos if there isn't.
-- hit - Whether there was a plane intersection. Value is true if there was, false if not.
function PlaneIntersection(vectorPos)
local hit = false
local currCamera = game.Workspace.CurrentCamera
local startPos = Vector3.new(
currCamera.CoordinateFrame.p.X,
currCamera.CoordinateFrame.p.Y,
currCamera.CoordinateFrame.p.Z
)
local endPos = Vector3.new(vectorPos.X, vectorPos.Y, vectorPos.Z)
local normal = Vector3.new(0, 1, 0)
local p3 = Vector3.new(0, 0, 0)
local startEndDot = normal:Dot(endPos - startPos)
local cellPos = vectorPos
if startEndDot ~= 0 then
local t = normal:Dot(p3 - startPos) / startEndDot
if t >= 0 and t <= 1 then
local intersection = ((endPos - startPos) * t) + startPos
cellPos = game.Workspace.Terrain:WorldToCell(intersection)
hit = true
end
end
return cellPos, hit
end
-- Purpose:
-- Checks for terrain touched by the mouse hit.
-- Will do a plane intersection if no terrain is touched.
--
-- mouse - Mouse to check the .hit for.
--
-- Return:
-- cellPos - Cell position hit. Nil if none.
function GetTerrainForMouse(mouse)
-- There was no target, so all it could be is a plane intersection.
-- Check for a plane intersection. If there isn't one then nothing will get hit.
local cell = game.Workspace.Terrain:WorldToCellPreferSolid(
Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z)
)
local planeLoc
-- If nothing was hit, do the plane intersection.
if 0 == game.Workspace.Terrain:GetCell(cell.X, cell.Y, cell.Z).Value then
cell = nil
planeLoc, hit = PlaneIntersection(
Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z)
)
if hit then
cell = planeLoc
end
end
return cell
end
-- setup helper functions
local insertBoundingBoxOverlapVector = Vector3.new(0.3, 0.3, 0.3) -- we can still stamp if our character extrudes into the target stamping space by .3 or fewer units
-- rotates a model by yAngle radians about the global y-axis
local function rotatePartAndChildren(part, rotCF, offsetFromOrigin)
-- rotate this thing, if it's a part
if part:IsA "BasePart" then
part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin))
+ offsetFromOrigin
end
-- recursively do the same to all children
local partChildren = part:GetChildren()
for c = 1, #partChildren do
rotatePartAndChildren(partChildren[c], rotCF, offsetFromOrigin)
end
end
local function modelRotate(model, yAngle)
local rotCF = CFrame.Angles(0, yAngle, 0)
local offsetFromOrigin = model:GetModelCFrame().p
rotatePartAndChildren(model, rotCF, offsetFromOrigin)
end
local function collectParts(object, baseParts, scripts, decals)
if object:IsA "BasePart" then
baseParts[#baseParts + 1] = object
elseif object:IsA "Script" then
scripts[#scripts + 1] = object
elseif object:IsA "Decal" then
decals[#decals + 1] = object
end
for _, child in pairs(object:GetChildren()) do
collectParts(child, baseParts, scripts, decals)
end
end
local function clusterPartsInRegion(startVector, endVector)
local cluster = game.Workspace:FindFirstChild "Terrain"
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
local function findSeatsInModel(parent, seatTable)
if not parent then
return
end
if parent.className == "Seat" or parent.className == "VehicleSeat" then
table.insert(seatTable, parent)
end
local myChildren = parent:GetChildren()
for j = 1, #myChildren do
findSeatsInModel(myChildren[j], seatTable)
end
end
local function setSeatEnabledStatus(model, isEnabled)
local seatList = {}
findSeatsInModel(model, seatList)
if isEnabled then
-- remove any welds called "SeatWeld" in seats
for i = 1, #seatList do
local nextSeat = seatList[i]:FindFirstChild "SeatWeld"
while nextSeat do
nextSeat:Remove()
nextSeat = seatList[i]:FindFirstChild "SeatWeld"
end
end
else
-- put a weld called "SeatWeld" in every seat
-- this tricks it into thinking there's already someone sitting there, and it won't make you sit XD
for i = 1, #seatList do
local fakeWeld = Instance.new "Weld"
fakeWeld.Name = "SeatWeld"
fakeWeld.Parent = seatList[i]
end
end
end
local function autoAlignToFace(parts)
local aatf = parts:FindFirstChild "AutoAlignToFace"
if aatf then
return aatf.Value
else
return false
end
end
local function getClosestAlignedWorldDirection(aVector3InWorld)
local xDir = Vector3.new(1, 0, 0)
local yDir = Vector3.new(0, 1, 0)
local zDir = Vector3.new(0, 0, 1)
local xDot = aVector3InWorld.x * xDir.x
+ aVector3InWorld.y * xDir.y
+ aVector3InWorld.z * xDir.z
local yDot = aVector3InWorld.x * yDir.x
+ aVector3InWorld.y * yDir.y
+ aVector3InWorld.z * yDir.z
local zDot = aVector3InWorld.x * zDir.x
+ aVector3InWorld.y * zDir.y
+ aVector3InWorld.z * zDir.z
if math.abs(xDot) > math.abs(yDot) and math.abs(xDot) > math.abs(zDot) then
if xDot > 0 then
return 0
else
return 3
end
elseif
math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs(zDot)
then
if yDot > 0 then
return 1
else
return 4
end
else
if zDot > 0 then
return 2
else
return 5
end
end
end
local function positionPartsAtCFrame3(aCFrame, currentParts)
local insertCFrame
if not currentParts then
return currentParts
end
if
currentParts and (currentParts:IsA "Model" or currentParts:IsA "Tool")
then
insertCFrame = currentParts:GetModelCFrame()
currentParts:TranslateBy(aCFrame.p - insertCFrame.p)
else
currentParts.CFrame = aCFrame
end
return currentParts
end
local function calcRayHitTime(rayStart, raySlope, intersectionPlane)
if math.abs(raySlope) < 0.01 then
return 0
end -- 0 slope --> we just say intersection time is 0, and sidestep this dimension
return (intersectionPlane - rayStart) / raySlope
end
local function modelTargetSurface(partOrModel, rayStart, rayEnd)
if not partOrModel then
return 0
end
local modelCFrame
local modelSize
if partOrModel:IsA "Model" then
modelCFrame = partOrModel:GetModelCFrame()
modelSize = partOrModel:GetModelSize()
else
modelCFrame = partOrModel.CFrame
modelSize = partOrModel.Size
end
local mouseRayStart = modelCFrame:pointToObjectSpace(rayStart)
local mouseRayEnd = modelCFrame:pointToObjectSpace(rayEnd)
local mouseSlope = mouseRayEnd - mouseRayStart
local xPositive = 1
local yPositive = 1
local zPositive = 1
if mouseSlope.X > 0 then
xPositive = -1
end
if mouseSlope.Y > 0 then
yPositive = -1
end
if mouseSlope.Z > 0 then
zPositive = -1
end
-- find which surface the transformed mouse ray hits (using modelSize):
local xHitTime = calcRayHitTime(
mouseRayStart.X,
mouseSlope.X,
modelSize.X / 2 * xPositive
)
local yHitTime = calcRayHitTime(
mouseRayStart.Y,
mouseSlope.Y,
modelSize.Y / 2 * yPositive
)
local zHitTime = calcRayHitTime(
mouseRayStart.Z,
mouseSlope.Z,
modelSize.Z / 2 * zPositive
)
local hitFace = 0
--if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0 then
if xHitTime > yHitTime then
if xHitTime > zHitTime then
-- xFace is hit
hitFace = 1 * xPositive
else
-- zFace is hit
hitFace = 3 * zPositive
end
else
if yHitTime > zHitTime then
-- yFace is hit
hitFace = 2 * yPositive
else
-- zFace is hit
hitFace = 3 * zPositive
end
end
return hitFace
end
local 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 "Terrain" then
minVec = Vector3.new(-2, -2, -2)
maxVec = Vector3.new(2, 2, 2)
elseif partOrModel:IsA "BasePart" then
minVec = -0.5 * partOrModel.Size
maxVec = -minVec
else
maxVec = partOrModel:GetModelSize() * 0.5
minVec = -maxVec
end
-- Adjust bounding box to reflect what the model or part author wants in terms of justification
local justifyValue = partOrModel:FindFirstChild "Justification"
if justifyValue ~= nil then
-- find the multiple of 4 that contains the model
local justify = justifyValue.Value
local two = Vector3.new(2, 2, 2)
local actualBox = maxVec - minVec - Vector3.new(0.01, 0.01, 0.01)
local containingGridBox = Vector3.new(
4 * math.ceil(actualBox.x / 4),
4 * math.ceil(actualBox.y / 4),
4 * math.ceil(actualBox.z / 4)
)
local adjustment = containingGridBox - actualBox
minVec -= 0.5 * adjustment * justify
maxVec += 0.5 * adjustment * (two - justify)
end
return minVec, maxVec
end
local function getBoundingBoxInWorldCoordinates(partOrModel)
local minVec = Vector3.new(math.huge, math.huge, math.huge)
local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)
if partOrModel:IsA "BasePart" and not partOrModel:IsA "Terrain" then
local vec1 =
partOrModel.CFrame:pointToWorldSpace(-0.5 * partOrModel.Size)
local vec2 =
partOrModel.CFrame:pointToWorldSpace(0.5 * partOrModel.Size)
minVec = Vector3.new(
math.min(vec1.X, vec2.X),
math.min(vec1.Y, vec2.Y),
math.min(vec1.Z, vec2.Z)
)
maxVec = Vector3.new(
math.max(vec1.X, vec2.X),
math.max(vec1.Y, vec2.Y),
math.max(vec1.Z, vec2.Z)
)
elseif not partOrModel:IsA "Terrain" then
-- we shouldn't have to deal with this case
--minVec = Vector3.new(-2, -2, -2)
--maxVec = Vector3.new(2, 2, 2)
-- else
local vec1 = partOrModel
:GetModelCFrame()
:pointToWorldSpace(-0.5 * partOrModel:GetModelSize())
local vec2 = partOrModel
:GetModelCFrame()
:pointToWorldSpace(0.5 * partOrModel:GetModelSize())
minVec = Vector3.new(
math.min(vec1.X, vec2.X),
math.min(vec1.Y, vec2.Y),
math.min(vec1.Z, vec2.Z)
)
maxVec = Vector3.new(
math.max(vec1.X, vec2.X),
math.max(vec1.Y, vec2.Y),
math.max(vec1.Z, vec2.Z)
)
end
return minVec, maxVec
end
local function getTargetPartBoundingBox(targetPart)
if targetPart.Parent:FindFirstChild "RobloxModel" ~= nil then
return getBoundingBox2(targetPart.Parent)
else
return getBoundingBox2(targetPart)
end
end
local function getMouseTargetCFrame(targetPart)
if targetPart.Parent:FindFirstChild "RobloxModel" ~= nil then
if targetPart.Parent:IsA "Tool" then
return targetPart.Parent.Handle.CFrame
else
return targetPart.Parent:GetModelCFrame()
end
else
return targetPart.CFrame
end
end
local function isBlocker(part) -- returns whether or not we want to cancel the stamp because we're blocked by this part
if not part then
return false
end
if not part.Parent then
return false
end
if part:FindFirstChild "Humanoid" then
return false
end
if
part:FindFirstChild "RobloxStamper" or part:FindFirstChild "RobloxModel"
then
return true
end
if part:IsA "Part" and not part.CanCollide then
return false
end
if part == game.Lighting then
return false
end
return isBlocker(part.Parent)
end
-- helper function to determine if a character can be pushed upwards by a certain amount
-- character is 5 studs tall, we'll check a 1.5 x 1.5 x 4.5 box around char, with center .5 studs below torsocenter
local function spaceAboveCharacter(charTorso, newTorsoY, stampData)
local partsAboveChar = game.Workspace:FindPartsInRegion3(
Region3.new(
Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z)
- Vector3.new(0.75, 2.75, 0.75),
Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z)
+ Vector3.new(0.75, 1.75, 0.75)
),
charTorso.Parent,
100
)
for j = 1, #partsAboveChar do
if
partsAboveChar[j].CanCollide
and not partsAboveChar[j]:IsDescendantOf(stampData.CurrentParts)
then
return false
end
end
if
clusterPartsInRegion(
Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z)
- Vector3.new(0.75, 2.75, 0.75),
Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z)
+ Vector3.new(0.75, 1.75, 0.75)
)
then
return false
end
return true
end
local function findConfigAtMouseTarget(Mouse, stampData)
-- *Critical Assumption* :
-- This function assumes the target CF axes are orthogonal with the target bounding box faces
-- And, it assumes the insert CF axes are orthongonal with the insert bounding box faces
-- Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor
-- will it work for parts in a model that are not orthogonally aligned with the model's CF.
if not Mouse then
return nil
end -- This can happen sometimes, return if so
if not stampData then
error "findConfigAtMouseTarget: stampData is nil"
return nil
end
if not stampData.CurrentParts then
return nil
end
local grid = 4.0
local admissibleConfig = false
local targetConfig = CFrame.new(0, 0, 0)
local minBB, maxBB = getBoundingBox2(stampData.CurrentParts)
local diagBB = maxBB - minBB
local insertCFrame
if
stampData.CurrentParts:IsA "Model" or stampData.CurrentParts:IsA "Tool"
then
insertCFrame = stampData.CurrentParts:GetModelCFrame()
else
insertCFrame = stampData.CurrentParts.CFrame
end
if Mouse then
if stampData.CurrentParts:IsA "Tool" then
Mouse.TargetFilter = stampData.CurrentParts.Handle
else
Mouse.TargetFilter = stampData.CurrentParts
end
end
local hitPlane = false
local targetPart
local success = pcall(function()
targetPart = Mouse.Target
end)
if not success then -- or targetPart == nil then
return admissibleConfig, targetConfig
end
local mouseHitInWorld = Vector3.new(0, 0, 0)
if Mouse then
mouseHitInWorld = Vector3.new(Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z)
end
local cellPos
-- Nothing was hit, so check for the default plane.
if nil == targetPart then
cellPos = GetTerrainForMouse(Mouse)
if nil == cellPos then
hitPlane = false
return admissibleConfig, targetConfig
else
targetPart = game.Workspace.Terrain
hitPlane = true
-- Take into account error that will occur.
cellPos = Vector3.new(cellPos.X - 1, cellPos.Y, cellPos.Z)
mouseHitInWorld = game.Workspace.Terrain:CellCenterToWorld(
cellPos.x,
cellPos.y,
cellPos.z
)
end
end
-- test mouse hit location
local minBBTarget, maxBBTarget = getTargetPartBoundingBox(targetPart)
local diagBBTarget = maxBBTarget - minBBTarget
local targetCFrame = getMouseTargetCFrame(targetPart)
if targetPart:IsA "Terrain" then
if not cluster then
cluster = game.Workspace:FindFirstChild "Terrain"
end
local cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld)
if hitPlane then
cellID = cellPos
end
targetCFrame = CFrame.new(
game.Workspace.Terrain:CellCenterToWorld(
cellID.x,
cellID.y,
cellID.z
)
)
end
local mouseHitInTarget = targetCFrame:pointToObjectSpace(mouseHitInWorld)
local targetVectorInWorld = Vector3.new(0, 0, 0)
if Mouse then
-- DON'T WANT THIS IN TERMS OF THE MODEL CFRAME! (.TargetSurface is in terms of the part CFrame, so this would break, right? [HotThoth])
-- (ideally, we would want to make the Mouse.TargetSurface a model-targetsurface instead, but for testing will be using the converse)
--targetVectorInWorld = targetCFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))
targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace(
Vector3.FromNormalId(Mouse.TargetSurface)
) -- better, but model cframe would be best
--[[if targetPart.Parent:IsA("Model") then
local hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- best, if you get it right
local WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
if hitFace > 0 then
targetVectorInWorld = targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace])
elseif hitFace < 0 then
targetVectorInWorld = targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace])
end
end]]
end
local targetRefPointInTarget, insertRefPointInInsert
local clampToSurface
if getClosestAlignedWorldDirection(targetVectorInWorld) == 0 then
targetRefPointInTarget =
targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
insertRefPointInInsert =
insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
clampToSurface = Vector3.new(0, 1, 1)
elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 3 then
targetRefPointInTarget =
targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
insertRefPointInInsert =
insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
clampToSurface = Vector3.new(0, 1, 1)
elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 1 then
targetRefPointInTarget =
targetCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
insertRefPointInInsert =
insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
clampToSurface = Vector3.new(1, 0, 1)
elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 4 then
targetRefPointInTarget =
targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
insertRefPointInInsert =
insertCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
clampToSurface = Vector3.new(1, 0, 1)
elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 2 then
targetRefPointInTarget =
targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
insertRefPointInInsert =
insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
clampToSurface = Vector3.new(1, 1, 0)
else
targetRefPointInTarget =
targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
insertRefPointInInsert =
insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
clampToSurface = Vector3.new(1, 1, 0)
end
targetRefPointInTarget *= (0.5 * diagBBTarget) + 0.5 * (maxBBTarget + minBBTarget)
insertRefPointInInsert *= (0.5 * diagBB) + 0.5 * (maxBB + minBB)
-- To Do: For cases that are not aligned to the world grid, account for the minimal rotation
-- needed to bring the Insert part(s) into alignment with the Target Part
-- Apply the rotation here
local delta = mouseHitInTarget - targetRefPointInTarget
local deltaClamped = Vector3.new(
grid * math.modf(delta.x / grid),
grid * math.modf(delta.y / grid),
grid * math.modf(delta.z / grid)
)
deltaClamped *= clampToSurface
local targetTouchInTarget = deltaClamped + targetRefPointInTarget
local TargetTouchRelToWorld =
targetCFrame:pointToWorldSpace(targetTouchInTarget)
local InsertTouchInWorld =
insertCFrame:vectorToWorldSpace(insertRefPointInInsert)
local posInsertOriginInWorld = TargetTouchRelToWorld - InsertTouchInWorld
local _, _, _, R00, R01, R02, R10, R11, R12, R20, R21, R22 =
insertCFrame:components()
targetConfig = CFrame.new(
posInsertOriginInWorld.x,
posInsertOriginInWorld.y,
posInsertOriginInWorld.z,
R00,
R01,
R02,
R10,
R11,
R12,
R20,
R21,
R22
)
admissibleConfig = true
return admissibleConfig,
targetConfig,
getClosestAlignedWorldDirection(targetVectorInWorld)
end
local function truncateToCircleEighth(bigValue, littleValue)
local big = math.abs(bigValue)
local little = math.abs(littleValue)
local hypotenuse = math.sqrt(big * big + little * little)
local frac = little / hypotenuse
local bigSign = 1
local littleSign = 1
if bigValue < 0 then
bigSign = -1
end
if littleValue < 0 then
littleSign = -1
end
if frac > 0.382683432 then
-- between 22.5 and 45 degrees, so truncate to 45-degree tilt
return 0.707106781 * hypotenuse * bigSign,
0.707106781 * hypotenuse * littleSign
else
-- between 0 and 22.5 degrees, so truncate to 0-degree tilt
return hypotenuse * bigSign, 0
end
end
local function saveTheWelds(object, manualWeldTable, manualWeldParentTable)
if object:IsA "ManualWeld" or object:IsA "Rotate" then
table.insert(manualWeldTable, object)
table.insert(manualWeldParentTable, object.Parent)
else
local children = object:GetChildren()
for i = 1, #children do
saveTheWelds(children[i], manualWeldTable, manualWeldParentTable)
end
end
end
local function restoreTheWelds(manualWeldTable, manualWeldParentTable)
for i = 1, #manualWeldTable do
manualWeldTable[i].Parent = manualWeldParentTable[i]
end
end
t.CanEditRegion = function(partOrModel, EditRegion) -- todo: use model and stamper metadata
if not EditRegion then
return true, false
end
local minBB, maxBB = getBoundingBoxInWorldCoordinates(partOrModel)
if
minBB.X < EditRegion.CFrame.p.X - EditRegion.Size.X / 2
or minBB.Y < EditRegion.CFrame.p.Y - EditRegion.Size.Y / 2
or minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z / 2
then
return false, false
end
if
maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X / 2
or maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y / 2
or maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z / 2
then
return false, false
end
return true, false
end
t.GetStampModel = function(assetId, terrainShape, useAssetVersionId)
if assetId == 0 then
return nil, "No Asset"
end
if assetId < 0 then
return nil, "Negative Asset"
end
local function UnlockInstances(object)
if object:IsA "BasePart" then
object.Locked = false
end
for _, child in pairs(object:GetChildren()) do
UnlockInstances(child)
end
end
local function getClosestColorToTerrainMaterial(terrainValue)
if terrainValue == 1 then
return BrickColor.new "Bright green"
elseif terrainValue == 2 then
return BrickColor.new "Bright yellow"
elseif terrainValue == 3 then
return BrickColor.new "Bright red"
elseif terrainValue == 4 then
return BrickColor.new "Sand red"
elseif terrainValue == 5 then
return BrickColor.new "Black"
elseif terrainValue == 6 then
return BrickColor.new "Dark stone grey"
elseif terrainValue == 7 then
return BrickColor.new "Sand blue"
elseif terrainValue == 8 then
return BrickColor.new "Deep orange"
elseif terrainValue == 9 then
return BrickColor.new "Dark orange"
elseif terrainValue == 10 then
return BrickColor.new "Reddish brown"
elseif terrainValue == 11 then
return BrickColor.new "Light orange"
elseif terrainValue == 12 then
return BrickColor.new "Light stone grey"
elseif terrainValue == 13 then
return BrickColor.new "Sand green"
elseif terrainValue == 14 then
return BrickColor.new "Medium stone grey"
elseif terrainValue == 15 then
return BrickColor.new "Really red"
elseif terrainValue == 16 then
return BrickColor.new "Really blue"
elseif terrainValue == 17 then
return BrickColor.new "Bright blue"
else
return BrickColor.new "Bright green"
end
end
local function setupFakeTerrainPart(cellMat, cellType, cellOrient)
local newTerrainPiece
if cellType == 1 or cellType == 4 then
newTerrainPiece = Instance.new "WedgePart"
newTerrainPiece.formFactor = "Custom"
elseif cellType == 2 then
newTerrainPiece = Instance.new "CornerWedgePart"
else
newTerrainPiece = Instance.new "Part"
newTerrainPiece.formFactor = "Custom"
end
newTerrainPiece.Name = "MegaClusterCube"
newTerrainPiece.Size = Vector3.new(4, 4, 4)
newTerrainPiece.BottomSurface = "Smooth"
newTerrainPiece.TopSurface = "Smooth"
-- can add decals or textures here if feeling particularly adventurous... for now, can make a table of look-up colors
newTerrainPiece.BrickColor = getClosestColorToTerrainMaterial(cellMat)
local sideways = 0
local flipped = math.pi
if cellType == 4 then
sideways = -math.pi / 2
end
if cellType == 2 or cellType == 3 then
flipped = 0
end
newTerrainPiece.CFrame =
CFrame.Angles(0, math.pi / 2 * cellOrient + flipped, sideways)
if cellType == 3 then
local inverseCornerWedgeMesh = Instance.new "SpecialMesh"
inverseCornerWedgeMesh.MeshType = "FileMesh"
inverseCornerWedgeMesh.MeshId =
"http://banland.xyz/asset?id=66832495"
inverseCornerWedgeMesh.Scale = Vector3.new(2, 2, 2)
inverseCornerWedgeMesh.Parent = newTerrainPiece
end
local materialTag = Instance.new "Vector3Value"
materialTag.Value = Vector3.new(cellMat, cellType, cellOrient)
materialTag.Name = "ClusterMaterial"
materialTag.Parent = newTerrainPiece
return newTerrainPiece
end
-- This call will cause a "wait" until the data comes back
-- below we wait a max of 8 seconds before deciding to bail out on loading
local root
local loader
local loading = true
if useAssetVersionId then
loader = coroutine.create(function()
root = game:GetService("InsertService"):LoadAssetVersion(assetId)
loading = false
end)
coroutine.resume(loader)
else
loader = coroutine.create(function()
root = game:GetService("InsertService"):LoadAsset(assetId)
loading = false
end)
coroutine.resume(loader)
end
local lastGameTime = 0
local totalTime = 0
local maxWait = 8
while loading and totalTime < maxWait do
lastGameTime = tick()
wait(1)
totalTime += tick() - lastGameTime
end
loading = false
if totalTime >= maxWait then
return nil, "Load Time Fail"
end
if root == nil then
return nil, "Load Asset Fail"
end
if not root:IsA "Model" then
return nil, "Load Type Fail"
end
local instances = root:GetChildren()
if #instances == 0 then
return nil, "Empty Model Fail"
end
--Unlock all parts that are inserted, to make sure they are editable
UnlockInstances(root)
--Continue the insert process
root = root:GetChildren()[1]
--Examine the contents and decide what it looks like
for _, instance in pairs(instances) do
if instance:IsA "Team" then
instance.Parent = game:GetService "Teams"
elseif instance:IsA "Sky" then
local lightingService = game:GetService "Lighting"
for _, child in pairs(lightingService:GetChildren()) do
if child:IsA "Sky" then
child:Remove()
end
end
instance.Parent = lightingService
return
end
end
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if root:FindFirstChild "RobloxModel" == nil then
local stringTag = Instance.new "BoolValue"
stringTag.Name = "RobloxModel"
stringTag.Parent = root
if root:FindFirstChild "RobloxStamper" == nil then
local stringTag2 = Instance.new "BoolValue"
stringTag2.Name = "RobloxStamper"
stringTag2.Parent = root
end
end
if terrainShape then
if root.Name == "MegaClusterCube" then
if terrainShape == 6 then -- insert an autowedging tag
local autowedgeTag = Instance.new "BoolValue"
autowedgeTag.Name = "AutoWedge"
autowedgeTag.Parent = root
else
local clusterTag = root:FindFirstChild "ClusterMaterial"
if clusterTag then
if clusterTag:IsA "Vector3Value" then
root = setupFakeTerrainPart(
clusterTag.Value.X,
terrainShape,
clusterTag.Value.Z
)
else
root = setupFakeTerrainPart(
clusterTag.Value,
terrainShape,
0
)
end
else
root = setupFakeTerrainPart(1, terrainShape, 0)
end
end
end
end
return root
end
t.SetupStamperDragger = function(
modelToStamp,
Mouse,
StampInModel,
AllowedStampRegion,
StampFailedFunc
)
if not modelToStamp then
error "SetupStamperDragger: modelToStamp (first arg) is nil! Should be a stamper model"
return nil
end
if not modelToStamp:IsA "Model" and not modelToStamp:IsA "BasePart" then
error "SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!"
return nil
end
if not Mouse then
error "SetupStamperDragger: Mouse (second arg) is nil! Should be a mouse object"
return nil
end
if not Mouse:IsA "Mouse" then
error "SetupStamperDragger: Mouse (second arg) is not of type Mouse!"
return nil
end
local stampInModel
local allowedStampRegion
local stampFailedFunc
if StampInModel then
if not StampInModel:IsA "Model" then
error "SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'"
return nil
end
if not AllowedStampRegion then
error "SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined"
return nil
end
stampFailedFunc = StampFailedFunc
stampInModel = StampInModel
allowedStampRegion = AllowedStampRegion
end
-- Init all state variables
local gInitial90DegreeRotations = 0
local stampData
local mouseTarget
local errorBox = Instance.new "SelectionBox"
errorBox.Color = BrickColor.new "Bright red"
errorBox.Transparency = 0
errorBox.Archivable = false
-- for megacluster MEGA STAMPING
local adornPart = Instance.new "Part"
adornPart.Parent = nil
adornPart.formFactor = "Custom"
adornPart.Size = Vector3.new(4, 4, 4)
adornPart.CFrame = CFrame.new()
adornPart.Archivable = false
local adorn = Instance.new "SelectionBox"
adorn.Color = BrickColor.new "Toothpaste"
adorn.Adornee = adornPart
adorn.Visible = true
adorn.Transparency = 0
adorn.Name = "HighScalabilityStamperLine"
adorn.Archivable = false
local HighScalabilityLine = {}
HighScalabilityLine.Start = nil
HighScalabilityLine.End = nil
HighScalabilityLine.Adorn = adorn
HighScalabilityLine.AdornPart = adornPart
HighScalabilityLine.InternalLine = nil
HighScalabilityLine.NewHint = true
HighScalabilityLine.MorePoints = { nil, nil }
HighScalabilityLine.MoreLines = { nil, nil }
HighScalabilityLine.Dimensions = 1
local control = {}
local movingLock = false
local stampUpLock = false
local unstampableSurface = false
local mouseCons = {}
local keyCon
local stamped = Instance.new "BoolValue"
stamped.Archivable = false
stamped.Value = false
local lastTarget = {}
lastTarget.TerrainOrientation = 0
lastTarget.CFrame = 0
local cellInfo = {}
cellInfo.Material = 1
cellInfo.clusterType = 0
cellInfo.clusterOrientation = 0
local function isMegaClusterPart()
if not stampData then
return false
end
if not stampData.CurrentParts then
return false
end
return (
stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
or (stampData.CurrentParts.Name == "MegaClusterCube")
)
end
local function DoHighScalabilityRegionSelect()
local megaCube = stampData.CurrentParts:FindFirstChild "MegaClusterCube"
if not megaCube then
if stampData.CurrentParts.Name ~= "MegaClusterCube" then
return
else
megaCube = stampData.CurrentParts
end
end
HighScalabilityLine.End = megaCube.CFrame.p
local line
local line2 = Vector3.new(0, 0, 0)
local line3 = Vector3.new(0, 0, 0)
if HighScalabilityLine.Dimensions == 1 then
-- extract the line from these positions and limit to a 2D plane made from 2 of the world axes
-- then use dominating axis to limit line to be at 45-degree intervals
-- will use this internal representation of the line for the actual stamping
line = (HighScalabilityLine.End - HighScalabilityLine.Start)
if math.abs(line.X) < math.abs(line.Y) then
if math.abs(line.X) < math.abs(line.Z) then
-- limit to Y/Z plane, domination unknown
local newY, newZ
if math.abs(line.Y) > math.abs(line.Z) then
newY, newZ = truncateToCircleEighth(line.Y, line.Z)
else
newZ, newY = truncateToCircleEighth(line.Z, line.Y)
end
line = Vector3.new(0, newY, newZ)
else
-- limit to X/Y plane, with Y dominating
local newY, newX = truncateToCircleEighth(line.Y, line.X)
line = Vector3.new(newX, newY, 0)
end
else
if math.abs(line.Y) < math.abs(line.Z) then
-- limit to X/Z plane, domination unknown
local newX, newZ
if math.abs(line.X) > math.abs(line.Z) then
newX, newZ = truncateToCircleEighth(line.X, line.Z)
else
newZ, newX = truncateToCircleEighth(line.Z, line.X)
end
line = Vector3.new(newX, 0, newZ)
else
-- limit to X/Y plane, with X dominating
local newX, newY = truncateToCircleEighth(line.X, line.Y)
line = Vector3.new(newX, newY, 0)
end
end
HighScalabilityLine.InternalLine = line
elseif HighScalabilityLine.Dimensions == 2 then
line = HighScalabilityLine.MoreLines[1]
line2 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[1]
-- take out any component of line2 along line1, so you get perpendicular to line1 component
line2 -= line.unit * line.unit:Dot(line2)
tempCFrame = CFrame.new(
HighScalabilityLine.Start,
HighScalabilityLine.Start + line
)
-- then zero out whichever is the smaller component
local yAxis = tempCFrame:vectorToWorldSpace(Vector3.new(0, 1, 0))
local xAxis = tempCFrame:vectorToWorldSpace(Vector3.new(1, 0, 0))
local xComp = xAxis:Dot(line2)
local yComp = yAxis:Dot(line2)
if math.abs(yComp) > math.abs(xComp) then
line2 -= xAxis * xComp
else
line2 -= yAxis * yComp
end
HighScalabilityLine.InternalLine = line2
elseif HighScalabilityLine.Dimensions == 3 then
line = HighScalabilityLine.MoreLines[1]
line2 = HighScalabilityLine.MoreLines[2]
line3 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[2]
-- zero out all components of previous lines
line3 -= line.unit * line.unit:Dot(line3)
line3 -= line2.unit * line2.unit:Dot(line3)
HighScalabilityLine.InternalLine = line3
end
-- resize the "line" graphic to be the correct size and orientation
tempCFrame = CFrame.new(
HighScalabilityLine.Start,
HighScalabilityLine.Start + line
)
if HighScalabilityLine.Dimensions == 1 then -- faster calculation for line
HighScalabilityLine.AdornPart.Size =
Vector3.new(4, 4, line.magnitude + 4)
HighScalabilityLine.AdornPart.CFrame = tempCFrame
+ tempCFrame:vectorToWorldSpace(
Vector3.new(2, 2, 2)
- HighScalabilityLine.AdornPart.Size / 2
)
else
local boxSize = tempCFrame:vectorToObjectSpace(line + line2 + line3)
HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, 4)
+ Vector3.new(
math.abs(boxSize.X),
math.abs(boxSize.Y),
math.abs(boxSize.Z)
)
HighScalabilityLine.AdornPart.CFrame = tempCFrame
+ tempCFrame:vectorToWorldSpace(boxSize / 2)
end
-- make player able to see this ish
local gui
if game.Players.LocalPlayer then
gui = game.Players.LocalPlayer:FindFirstChild "PlayerGui"
if gui and gui:IsA "PlayerGui" then
if
(HighScalabilityLine.Dimensions == 1 and line.magnitude > 3)
or HighScalabilityLine.Dimensions > 1
then -- don't show if mouse hasn't moved enough
HighScalabilityLine.Adorn.Parent = gui
end
end
end
if gui == nil then -- we are in studio
gui = game:GetService "CoreGui"
if
(HighScalabilityLine.Dimensions == 1 and line.magnitude > 3)
or HighScalabilityLine.Dimensions > 1
then -- don't show if mouse hasn't moved enough
HighScalabilityLine.Adorn.Parent = gui
end
end
end
local function DoStamperMouseMove(Mouse)
if not Mouse then
error "Error: RbxStamper.DoStamperMouseMove: Mouse is nil"
return
end
if not Mouse:IsA "Mouse" then
error(
"Error: RbxStamper.DoStamperMouseMove: Mouse is of type "
.. Mouse.className
.. " should be of type Mouse"
)
return
end
-- There wasn't a target (no part or terrain), so check for plane intersection.
if not Mouse.Target then
local cellPos = GetTerrainForMouse(Mouse)
if nil == cellPos then
return
end
end
if not stampData then
return
end
-- don't move with dragger - will move in one step on mouse down
-- draw ghost at acceptable positions
configFound, targetCFrame, targetSurface =
findConfigAtMouseTarget(Mouse, stampData)
if not configFound then
error "RbxStamper.DoStamperMouseMove No configFound, returning"
return
end
local numRotations = 0 -- update this according to how many rotations you need to get it to target surface
if
autoAlignToFace(stampData.CurrentParts)
and targetSurface ~= 1
and targetSurface ~= 4
then -- pre-rotate the flag or portrait so it's aligned correctly
if targetSurface == 3 then
numRotations = 0
- gInitial90DegreeRotations
+ autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 0 then
numRotations = 2
- gInitial90DegreeRotations
+ autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 5 then
numRotations = 3
- gInitial90DegreeRotations
+ autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 2 then
numRotations = 1
- gInitial90DegreeRotations
+ autoAlignToFace(stampData.CurrentParts)
end
end
local ry = math.pi / 2
gInitial90DegreeRotations += numRotations
if
stampData.CurrentParts:IsA "Model"
or stampData.CurrentParts:IsA "Tool"
then
--stampData.CurrentParts:Rotate(0, ry*numRotations, 0)
modelRotate(stampData.CurrentParts, ry * numRotations)
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(
0,
ry * numRotations,
0
) * stampData.CurrentParts.CFrame
end
-- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE
local minBB, maxBB =
getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
-- need to offset by distance to be dragged
local currModelCFrame
if stampData.CurrentParts:IsA "Model" then
currModelCFrame = stampData.CurrentParts:GetModelCFrame()
else
currModelCFrame = stampData.CurrentParts.CFrame
end
minBB += targetCFrame.p - currModelCFrame.p
maxBB += targetCFrame.p - currModelCFrame.p
-- don't drag into terrain
if
clusterPartsInRegion(
minBB + insertBoundingBoxOverlapVector,
maxBB - insertBoundingBoxOverlapVector
)
then
if lastTarget.CFrame then
if
stampData.CurrentParts:FindFirstChild(
"ClusterMaterial",
true
)
then
local theClusterMaterial =
stampData.CurrentParts:FindFirstChild(
"ClusterMaterial",
true
)
if theClusterMaterial:IsA "Vector3Value" then
local stampClusterMaterial =
stampData.CurrentParts:FindFirstChild(
"ClusterMaterial",
true
)
if stampClusterMaterial then
stampClusterMaterial = clusterMat
end
end
end
end
return
end
-- if we are stamping a terrain part, make sure it goes on the grid! Otherwise preview block could be placed off grid, but stamped on grid
if isMegaClusterPart() then
local cellToStamp =
game.Workspace.Terrain:WorldToCell(targetCFrame.p)
local newCFramePosition = game.Workspace.Terrain:CellCenterToWorld(
cellToStamp.X,
cellToStamp.Y,
cellToStamp.Z
)
local _, _, _, R00, R01, R02, R10, R11, R12, R20, R21, R22 =
targetCFrame:components()
targetCFrame = CFrame.new(
newCFramePosition.X,
newCFramePosition.Y,
newCFramePosition.Z,
R00,
R01,
R02,
R10,
R11,
R12,
R20,
R21,
R22
)
end
positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
lastTarget.CFrame = targetCFrame -- successful positioning, so update 'dat cframe
if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
local clusterMat =
stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterMat:IsA "Vector3Value" then
lastTarget.TerrainOrientation = clusterMat.Value.Z
end
end
-- auto break joints code
if Mouse and Mouse.Target and Mouse.Target.Parent then
local modelInfo = Mouse.Target:FindFirstChild "RobloxModel"
if not modelInfo then
modelInfo = Mouse.Target.Parent:FindFirstChild "RobloxModel"
end
local myModelInfo =
stampData.CurrentParts:FindFirstChild "UnstampableFaces"
--if (modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then -- need better targetSurface calcs
if true then
local breakingFaces = ""
local myBreakingFaces = ""
if
modelInfo
and modelInfo.Parent:FindFirstChild "UnstampableFaces"
then
breakingFaces = modelInfo.Parent.UnstampableFaces.Value
end
if myModelInfo then
myBreakingFaces = myModelInfo.Value
end
local hitFace = 0
if modelInfo then
hitFace = modelTargetSurface(
modelInfo.Parent,
game.Workspace.CurrentCamera.CoordinateFrame.p,
Mouse.Hit.p
)
end
-- are we stamping TO an unstampable surface?
for bf in string.gmatch(breakingFaces, "[^,]+") do
if hitFace == tonumber(bf) then
-- return before we hit the JointsService code below!
unstampableSurface = true
game.JointsService:ClearJoinAfterMoveJoints() -- clear the JointsService cache
return
end
end
-- now we have to cast the ray back in the other direction to find the surface we're stamping FROM
hitFace = modelTargetSurface(
stampData.CurrentParts,
Mouse.Hit.p,
game.Workspace.CurrentCamera.CoordinateFrame.p
)
-- are we stamping WITH an unstampable surface?
for bf in string.gmatch(myBreakingFaces, "[^,]+") do
if hitFace == tonumber(bf) then
unstampableSurface = true
game.JointsService:ClearJoinAfterMoveJoints() -- clear the JointsService cache
return
end
end
-- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame
-- targetSurface: 1 is top, 4 is bottom,
end
end
-- to show joints during the mouse move
unstampableSurface = false
game.JointsService:SetJoinAfterMoveInstance(stampData.CurrentParts)
-- most common mouse inactive error occurs here, so check mouse active one more time in a pcall
if
not pcall(function()
if
Mouse
and Mouse.Target
and Mouse.Target.Parent:FindFirstChild "RobloxModel"
== nil
then
return
else
return
end
end)
then
error "Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check"
game.JointsService:ClearJoinAfterMoveJoints()
Mouse = nil
return
end
if
Mouse
and Mouse.Target
and Mouse.Target.Parent:FindFirstChild "RobloxModel" == nil
then
game.JointsService:SetJoinAfterMoveTarget(Mouse.Target)
else
game.JointsService:SetJoinAfterMoveTarget(nil)
end
game.JointsService:ShowPermissibleJoints()
-- here we allow for a line of high-scalability parts
if
isMegaClusterPart()
and HighScalabilityLine
and HighScalabilityLine.Start
then
DoHighScalabilityRegionSelect()
end
end
local function setupKeyListener(key, Mouse)
if control and control.Paused then
return
end -- don't do this if we have no stamp
key = string.lower(key)
if key == "r" and not autoAlignToFace(stampData.CurrentParts) then -- rotate the model
gInitial90DegreeRotations += 1
-- Update orientation value if this is a fake terrain part
local clusterValues =
stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterValues and clusterValues:IsA "Vector3Value" then
clusterValues.Value = Vector3.new(
clusterValues.Value.X,
clusterValues.Value.Y,
(clusterValues.Value.Z + 1) % 4
)
end
-- Rotate the parts or all the parts in the model
local ry = math.pi / 2
if
stampData.CurrentParts:IsA "Model"
or stampData.CurrentParts:IsA "Tool"
then
--stampData.CurrentParts:Rotate(0, ry, 0)
modelRotate(stampData.CurrentParts, ry)
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(
0,
ry,
0
) * stampData.CurrentParts.CFrame
end
-- After rotating, update the position
configFound, targetCFrame =
findConfigAtMouseTarget(Mouse, stampData)
if configFound then
positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
-- update everything else in MouseMove
DoStamperMouseMove(Mouse)
end
elseif key == "c" then -- try to expand our high scalability dragger dimension
if
HighScalabilityLine.InternalLine
and HighScalabilityLine.InternalLine.magnitude > 0
and HighScalabilityLine.Dimensions < 3
then
HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] =
HighScalabilityLine.End
HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] =
HighScalabilityLine.InternalLine
HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions
+ 1
HighScalabilityLine.NewHint = true
end
end
end
keyCon = Mouse.KeyDown:connect(
function(key) -- init key connection (keeping code close to func)
setupKeyListener(key, Mouse)
end
)
local function resetHighScalabilityLine()
if HighScalabilityLine then
HighScalabilityLine.Start = nil
HighScalabilityLine.End = nil
HighScalabilityLine.InternalLine = nil
HighScalabilityLine.NewHint = true
end
end
local function flashRedBox()
local gui = game.CoreGui
if game:FindFirstChild "Players" then
if game.Players.LocalPlayer then
if game.Players.LocalPlayer:FindFirstChild "PlayerGui" then
gui = game.Players.LocalPlayer.PlayerGui
end
end
end
if not stampData.ErrorBox then
return
end
stampData.ErrorBox.Parent = gui
if stampData.CurrentParts:IsA "Tool" then
stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle
else
stampData.ErrorBox.Adornee = stampData.CurrentParts
end
delay(0, function()
for _ = 1, 3 do
if stampData.ErrorBox then
stampData.ErrorBox.Visible = true
end
wait(0.13)
if stampData.ErrorBox then
stampData.ErrorBox.Visible = false
end
wait(0.13)
end
if stampData.ErrorBox then
stampData.ErrorBox.Adornee = nil
stampData.ErrorBox.Parent = Tool
end
end)
end
local function DoStamperMouseDown(Mouse)
if not Mouse then
error "Error: RbxStamper.DoStamperMouseDown: Mouse is nil"
return
end
if not Mouse:IsA "Mouse" then
error(
"Error: RbxStamper.DoStamperMouseDown: Mouse is of type",
Mouse.className,
"should be of type Mouse"
)
return
end
if not stampData then
return
end
if isMegaClusterPart() then
if Mouse and HighScalabilityLine then
local megaCube = stampData.CurrentParts:FindFirstChild(
"MegaClusterCube",
true
)
local terrain = game.Workspace.Terrain
if megaCube then
HighScalabilityLine.Dimensions = 1
local tempCell = terrain:WorldToCell(megaCube.CFrame.p)
HighScalabilityLine.Start = terrain:CellCenterToWorld(
tempCell.X,
tempCell.Y,
tempCell.Z
)
return
else
HighScalabilityLine.Dimensions = 1
local tempCell =
terrain:WorldToCell(stampData.CurrentParts.CFrame.p)
HighScalabilityLine.Start = terrain:CellCenterToWorld(
tempCell.X,
tempCell.Y,
tempCell.Z
)
return
end
end
end
end
local function loadSurfaceTypes(part, surfaces)
part.TopSurface = surfaces[1]
part.BottomSurface = surfaces[2]
part.LeftSurface = surfaces[3]
part.RightSurface = surfaces[4]
part.FrontSurface = surfaces[5]
part.BackSurface = surfaces[6]
end
local function saveSurfaceTypes(part, myTable)
local tempTable = {}
tempTable[1] = part.TopSurface
tempTable[2] = part.BottomSurface
tempTable[3] = part.LeftSurface
tempTable[4] = part.RightSurface
tempTable[5] = part.FrontSurface
tempTable[6] = part.BackSurface
myTable[part] = tempTable
end
-- local function makeSurfaceUnjoinable(part, surface)
-- -- TODO: FILL OUT!
-- end
local function prepareModel(model)
if not model then
return nil
end
local gDesiredTrans = 0.7
local gStaticTrans = 1
local clone = model:Clone()
local scripts = {}
local parts = {}
local decals = {}
stampData = {}
stampData.DisabledScripts = {}
stampData.TransparencyTable = {}
stampData.MaterialTable = {}
stampData.CanCollideTable = {}
stampData.AnchoredTable = {}
stampData.ArchivableTable = {}
stampData.DecalTransparencyTable = {}
stampData.SurfaceTypeTable = {}
collectParts(clone, parts, scripts, decals)
if #parts <= 0 then
return nil, "no parts found in modelToStamp"
end
for _, script in pairs(scripts) do
if not script.Disabled then
script.Disabled = true
stampData.DisabledScripts[#stampData.DisabledScripts + 1] =
script
end
end
for _, part in pairs(parts) do
stampData.TransparencyTable[part] = part.Transparency
part.Transparency = gStaticTrans
+ (1 - gStaticTrans) * part.Transparency
stampData.MaterialTable[part] = part.Material
part.Material = Enum.Material.Plastic
stampData.CanCollideTable[part] = part.CanCollide
part.CanCollide = false
stampData.AnchoredTable[part] = part.Anchored
part.Anchored = true
stampData.ArchivableTable[part] = part.Archivable
part.Archivable = false
saveSurfaceTypes(part, stampData.SurfaceTypeTable)
local fadeInDelayTime = 0.5
local transFadeInTime = 0.5
delay(0, function()
wait(fadeInDelayTime) -- give it some time to be completely transparent
local begTime = tick()
local currTime = begTime
while
(currTime - begTime) < transFadeInTime
and part
and part:IsA "BasePart"
and part.Transparency > gDesiredTrans
do
local newTrans = 1
- (
((currTime - begTime) / transFadeInTime)
* (gStaticTrans - gDesiredTrans)
)
if
stampData.TransparencyTable
and stampData.TransparencyTable[part]
then
part.Transparency = newTrans
+ (1 - newTrans)
* stampData.TransparencyTable[part]
end
wait(0.03)
currTime = tick()
end
if part and part:IsA "BasePart" then
if
stampData.TransparencyTable
and stampData.TransparencyTable[part]
then
part.Transparency = gDesiredTrans
+ (1 - gDesiredTrans)
* stampData.TransparencyTable[part]
end
end
end)
end
for _, decal in pairs(decals) do
stampData.DecalTransparencyTable[decal] = decal.Transparency
decal.Transparency = gDesiredTrans
+ (1 - gDesiredTrans) * decal.Transparency
end
-- disable all seats
setSeatEnabledStatus(clone, true)
setSeatEnabledStatus(clone, false)
stampData.CurrentParts = clone
-- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame"
if autoAlignToFace(clone) then
stampData.CurrentParts:ResetOrientationToIdentity()
gInitial90DegreeRotations = 0
else -- pre-rotate if necessary
local ry = gInitial90DegreeRotations * math.pi / 2
if
stampData.CurrentParts:IsA "Model"
or stampData.CurrentParts:IsA "Tool"
then
--stampData.CurrentParts:Rotate(0, ry, 0)
modelRotate(stampData.CurrentParts, ry)
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(
0,
ry,
0
) * stampData.CurrentParts.CFrame
end
end
-- since we're cloning the old model instead of the new one, we will need to update the orientation based on the original value AND how many more
-- rotations we expect since then [either that or we need to store the just-stamped clusterMaterial.Value.Z somewhere]. This should fix the terrain rotation
-- issue (fingers crossed) [HotThoth]
local clusterMaterial =
stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterMaterial and clusterMaterial:IsA "Vector3Value" then
clusterMaterial.Value = Vector3.new(
clusterMaterial.Value.X,
clusterMaterial.Value.Y,
(clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4
)
end
-- After rotating, update the position
local configFound, targetCFrame =
findConfigAtMouseTarget(Mouse, stampData)
if configFound then
stampData.CurrentParts =
positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
end
-- to show joints during the mouse move
game.JointsService:SetJoinAfterMoveInstance(stampData.CurrentParts)
return clone, parts
end
local function checkTerrainBlockCollisions(
cellPos,
checkHighScalabilityStamp
)
local cellCenterToWorld = game.Workspace.Terrain.CellCenterToWorld
local cellCenter = cellCenterToWorld(
game.Workspace.Terrain,
cellPos.X,
cellPos.Y,
cellPos.Z
)
local cellBlockingParts = game.Workspace:FindPartsInRegion3(
Region3.new(
cellCenter
- Vector3.new(2, 2, 2)
+ insertBoundingBoxOverlapVector,
cellCenter
+ Vector3.new(2, 2, 2)
- insertBoundingBoxOverlapVector
),
stampData.CurrentParts,
100
)
local skipThisCell = false
for b = 1, #cellBlockingParts do
if isBlocker(cellBlockingParts[b]) then
skipThisCell = true
break
end
end
if not skipThisCell then
-- pop players up above any set cells
local alreadyPushedUp = {}
-- if no blocking model below, then see if stamping on top of a character
for b = 1, #cellBlockingParts do
if
cellBlockingParts[b].Parent
and not alreadyPushedUp[cellBlockingParts[b].Parent]
and cellBlockingParts[b].Parent:FindFirstChild "Humanoid"
and cellBlockingParts[b].Parent
:FindFirstChild("Humanoid")
:IsA "Humanoid"
then
-----------------------------------------------------------------------------------
local blockingPersonTorso =
cellBlockingParts[b].Parent:FindFirstChild "Torso"
alreadyPushedUp[cellBlockingParts[b].Parent] = true
if blockingPersonTorso then
-- if so, let's push the person upwards so they pop on top of the stamped model/part (but only if there's space above them)
local newY = cellCenter.Y + 5
if
spaceAboveCharacter(
blockingPersonTorso,
newY,
stampData
)
then
blockingPersonTorso.CFrame = blockingPersonTorso.CFrame
+ Vector3.new(
0,
newY - blockingPersonTorso.CFrame.p.Y,
0
)
else
-- if no space, we just skip this one
skipThisCell = true
break
end
end
-----------------------------------------------------------------------------------
end
end
end
if not skipThisCell then -- if we STILL aren't skipping... then we're good to go!
local canSetCell = true
if checkHighScalabilityStamp then -- check to see if cell is in region, if not we'll skip set
if allowedStampRegion then
local cellPos = cellCenterToWorld(
game.Workspace.Terrain,
cellPos.X,
cellPos.Y,
cellPos.Z
)
if
(
cellPos.X + 2
> allowedStampRegion.CFrame.p.X
+ allowedStampRegion.Size.X / 2
)
or (cellPos.X - 2 < allowedStampRegion.CFrame.p.X - allowedStampRegion.Size.X / 2)
or (cellPos.Y + 2 > allowedStampRegion.CFrame.p.Y + allowedStampRegion.Size.Y / 2)
or (cellPos.Y - 2 < allowedStampRegion.CFrame.p.Y - allowedStampRegion.Size.Y / 2)
or (cellPos.Z + 2 > allowedStampRegion.CFrame.p.Z + allowedStampRegion.Size.Z / 2)
or (
cellPos.Z - 2
< allowedStampRegion.CFrame.p.Z
- allowedStampRegion.Size.Z / 2
)
then
canSetCell = false
end
end
end
return canSetCell
end
return false
end
local function ResolveMegaClusterStamp(checkHighScalabilityStamp)
local cellSet = false
local cluster = game.Workspace.Terrain
local line = HighScalabilityLine.InternalLine
local cMax = game.Workspace.Terrain.MaxExtents.Max
local cMin = game.Workspace.Terrain.MaxExtents.Min
local clusterMaterial = 1 -- default is grass
local clusterType = 0 -- default is brick
local clusterOrientation = 0 -- default is 0 rotation
local autoWedgeClusterParts = false
if stampData.CurrentParts:FindFirstChild "AutoWedge" then
autoWedgeClusterParts = true
end
if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
clusterMaterial =
stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
if clusterMaterial:IsA "Vector3Value" then
clusterType = clusterMaterial.Value.Y
clusterOrientation = clusterMaterial.Value.Z
clusterMaterial = clusterMaterial.Value.X
elseif clusterMaterial:IsA "IntValue" then
clusterMaterial = clusterMaterial.Value
end
end
if
HighScalabilityLine.Adorn.Parent
and HighScalabilityLine.Start
and (
(HighScalabilityLine.Dimensions > 1)
or (line and line.magnitude > 0)
)
then
local startCell =
game.Workspace.Terrain:WorldToCell(HighScalabilityLine.Start)
local xInc = { 0, 0, 0 }
local yInc = { 0, 0, 0 }
local zInc = { 0, 0, 0 }
local incrementVect = { nil, nil, nil }
local stepVect = {
Vector3.new(0, 0, 0),
Vector3.new(0, 0, 0),
Vector3.new(0, 0, 0),
}
local worldAxes = {
Vector3.new(1, 0, 0),
Vector3.new(0, 1, 0),
Vector3.new(0, 0, 1),
}
local lines = {}
if HighScalabilityLine.Dimensions > 1 then
table.insert(lines, HighScalabilityLine.MoreLines[1])
end
if line and line.magnitude > 0 then
table.insert(lines, line)
end
if HighScalabilityLine.Dimensions > 2 then
table.insert(lines, HighScalabilityLine.MoreLines[2])
end
for i = 1, #lines do
lines[i] = Vector3.new(
math.floor(lines[i].X + 0.5),
math.floor(lines[i].Y + 0.5),
math.floor(lines[i].Z + 0.5)
) -- round to integers
if lines[i].X > 0 then
xInc[i] = 1
elseif lines[i].X < 0 then
xInc[i] = -1
end
if lines[i].Y > 0 then
yInc[i] = 1
elseif lines[i].Y < 0 then
yInc[i] = -1
end
if lines[i].Z > 0 then
zInc[i] = 1
elseif lines[i].Z < 0 then
zInc[i] = -1
end
incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i])
if incrementVect[i].magnitude < 0.9 then
incrementVect[i] = nil
end
end
if not lines[2] then
lines[2] = Vector3.new(0, 0, 0)
end
if not lines[3] then
lines[3] = Vector3.new(0, 0, 0)
end
local waterForceTag =
stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
local waterForceDirectionTag =
stampData.CurrentParts:FindFirstChild(
"WaterForceDirectionTag",
true
)
while stepVect[3].magnitude * 4 <= lines[3].magnitude do
local outerStepVectIndex = 1
while outerStepVectIndex < 4 do
stepVect[2] = Vector3.new(0, 0, 0)
while stepVect[2].magnitude * 4 <= lines[2].magnitude do
local innerStepVectIndex = 1
while innerStepVectIndex < 4 do
stepVect[1] = Vector3.new(0, 0, 0)
while
stepVect[1].magnitude * 4 <= lines[1].magnitude
do
local stepVectSum = stepVect[1]
+ stepVect[2]
+ stepVect[3]
local cellPos = Vector3int16.new(
startCell.X + stepVectSum.X,
startCell.Y + stepVectSum.Y,
startCell.Z + stepVectSum.Z
)
if
cellPos.X >= cMin.X
and cellPos.Y >= cMin.Y
and cellPos.Z >= cMin.Z
and cellPos.X < cMax.X
and cellPos.Y < cMax.Y
and cellPos.Z < cMax.Z
then
-- check if overlaps player or part
local okToStampTerrainBlock =
checkTerrainBlockCollisions(
cellPos,
checkHighScalabilityStamp
)
if okToStampTerrainBlock then
if waterForceTag then
cluster:SetWaterCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
Enum.WaterForce[waterForceTag.Value],
Enum.WaterDirection[waterForceDirectionTag.Value]
)
else
cluster:SetCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
clusterMaterial,
clusterType,
clusterOrientation
)
end
cellSet = true
-- auto-wedge it?
if autoWedgeClusterParts then
game.Workspace.Terrain:AutowedgeCells(
Region3int16.new(
Vector3int16.new(
cellPos.x - 1,
cellPos.y - 1,
cellPos.z - 1
),
Vector3int16.new(
cellPos.x + 1,
cellPos.y + 1,
cellPos.z + 1
)
)
)
end
end
end
stepVect[1] += incrementVect[1]
end
if incrementVect[2] then
while
innerStepVectIndex < 4
and worldAxes[innerStepVectIndex]:Dot(
incrementVect[2]
)
== 0
do
innerStepVectIndex += 1
end
if innerStepVectIndex < 4 then
stepVect[2] = stepVect[2]
+ worldAxes[innerStepVectIndex]
* worldAxes[innerStepVectIndex]:Dot(
incrementVect[2]
)
end
innerStepVectIndex += 1
else
stepVect[2] = Vector3.new(1, 0, 0)
innerStepVectIndex = 4 -- skip all remaining loops
end
if
stepVect[2].magnitude * 4 > lines[2].magnitude
then
innerStepVectIndex = 4
end
end
end
if incrementVect[3] then
while
outerStepVectIndex < 4
and worldAxes[outerStepVectIndex]:Dot(
incrementVect[3]
)
== 0
do
outerStepVectIndex += 1
end
if outerStepVectIndex < 4 then
stepVect[3] = stepVect[3]
+ worldAxes[outerStepVectIndex]
* worldAxes[outerStepVectIndex]:Dot(
incrementVect[3]
)
end
outerStepVectIndex += 1
else -- skip all remaining loops
stepVect[3] = Vector3.new(1, 0, 0)
outerStepVectIndex = 4
end
if stepVect[3].magnitude * 4 > lines[3].magnitude then
outerStepVectIndex = 4
end
end
end
end
-- and also get rid of any HighScalabilityLine stuff if it's there
HighScalabilityLine.Start = nil
HighScalabilityLine.Adorn.Parent = nil
-- Mark for undo.
if cellSet then
stampData.CurrentParts.Parent = nil
pcall(function()
ChangeHistoryService:SetWaypoint "StamperMulti"
end)
end
return cellSet
end
local function DoStamperMouseUp(Mouse)
if not Mouse then
error "Error: RbxStamper.DoStamperMouseUp: Mouse is nil"
return false
end
if not Mouse:IsA "Mouse" then
error(
"Error: RbxStamper.DoStamperMouseUp: Mouse is of type",
Mouse.className,
"should be of type Mouse"
)
return false
end
if not stampData.Dragger then
error "Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil"
return false
end
if not HighScalabilityLine then
return false
end
local checkHighScalabilityStamp
if stampInModel then
local canStamp
local isHSLPart = isMegaClusterPart()
if
isHSLPart
and HighScalabilityLine
and HighScalabilityLine.Start
and HighScalabilityLine.InternalLine
and HighScalabilityLine.InternalLine.magnitude > 0
then -- we have an HSL line, test later
canStamp = true
checkHighScalabilityStamp = true
else
canStamp, checkHighScalabilityStamp =
t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
end
if not canStamp then
if stampFailedFunc then
stampFailedFunc()
end
return false
end
end
-- if unstampable face, then don't let us stamp there!
if unstampableSurface then
flashRedBox()
return false
end
-- recheck if we can stamp, as we just moved part
canStamp, checkHighScalabilityStamp =
t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
if not canStamp then
if stampFailedFunc then
stampFailedFunc()
end
return false
end
-- Prevent part from being stamped on top of a player
local minBB, maxBB =
getBoundingBoxInWorldCoordinates(stampData.CurrentParts)
-- HotThoth's note: Now that above CurrentParts positioning has been commented out, to be truly correct, we would need to use the
-- value of configFound from the previous onStamperMouseMove call which moved the CurrentParts
-- Shouldn't this be true when lastTargetCFrame has been set and false otherwise?
configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
if configFound and not HighScalabilityLine.Adorn.Parent then
if
clusterPartsInRegion(
minBB + insertBoundingBoxOverlapVector,
maxBB - insertBoundingBoxOverlapVector
)
then
flashRedBox()
return false
end
local blockingParts = game.Workspace:FindPartsInRegion3(
Region3.new(
minBB + insertBoundingBoxOverlapVector,
maxBB - insertBoundingBoxOverlapVector
),
stampData.CurrentParts,
100
)
for b = 1, #blockingParts do
if isBlocker(blockingParts[b]) then
flashRedBox()
return false
end
end
local alreadyPushedUp = {}
-- if no blocking model below, then see if stamping on top of a character
for b = 1, #blockingParts do
if
blockingParts[b].Parent
and not alreadyPushedUp[blockingParts[b].Parent]
and blockingParts[b].Parent:FindFirstChild "Humanoid"
and blockingParts[b].Parent
:FindFirstChild("Humanoid")
:IsA "Humanoid"
then
---------------------------------------------------------------------------
local blockingPersonTorso =
blockingParts[b].Parent:FindFirstChild "Torso"
alreadyPushedUp[blockingParts[b].Parent] = true
if blockingPersonTorso then
-- if so, let's push the person upwards so they pop on top of the stamped model/part (but only if there's space above them)
local newY = maxBB.Y + 3
if
spaceAboveCharacter(
blockingPersonTorso,
newY,
stampData
)
then
blockingPersonTorso.CFrame = blockingPersonTorso.CFrame
+ Vector3.new(
0,
newY - blockingPersonTorso.CFrame.p.Y,
0
)
else
-- if no space, we just error
flashRedBox()
return false
end
end
---------------------------------------------------------------------------
end
end
elseif
not configFound
and not (
HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent
)
then -- if no config then only stamp if it's a real HSL!
resetHighScalabilityLine()
return false
end
-- something will be stamped! so set the "StampedSomething" toggle to true
if game:FindFirstChild "Players" then
if game.Players.LocalPlayer then
if game.Players.LocalPlayer.Character then
local localChar = game.Players.LocalPlayer.Character
local stampTracker = localChar:FindFirstChild "StampTracker"
if stampTracker and not stampTracker.Value then
stampTracker.Value = true
end
end
end
end
-- if we drew a line of mega parts, stamp them out
if
HighScalabilityLine.Start
and HighScalabilityLine.Adorn.Parent
and isMegaClusterPart()
then
if
ResolveMegaClusterStamp(checkHighScalabilityStamp)
or checkHighScalabilityStamp
then
-- kill the ghost part
stampData.CurrentParts.Parent = nil
return true
end
end
-- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff]
HighScalabilityLine.Start = nil
HighScalabilityLine.Adorn.Parent = nil
local cluster = game.Workspace.Terrain
-- if target point is in cluster, just use cluster:SetCell
if isMegaClusterPart() then
-- if targetCFrame is inside cluster, just set that cell to 1 and return
--local cellPos = cluster:WorldToCell(targetCFrame.p)
local cellPos
if stampData.CurrentParts:IsA "Model" then
cellPos = cluster:WorldToCell(
stampData.CurrentParts:GetModelCFrame().p
)
else
cellPos = cluster:WorldToCell(stampData.CurrentParts.CFrame.p)
end
local cMax = game.Workspace.Terrain.MaxExtents.Max
local cMin = game.Workspace.Terrain.MaxExtents.Min
if checkTerrainBlockCollisions(cellPos, false) then
local clusterValues = stampData.CurrentParts:FindFirstChild(
"ClusterMaterial",
true
)
local waterForceTag =
stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
local waterForceDirectionTag =
stampData.CurrentParts:FindFirstChild(
"WaterForceDirectionTag",
true
)
if
cellPos.X >= cMin.X
and cellPos.Y >= cMin.Y
and cellPos.Z >= cMin.Z
and cellPos.X < cMax.X
and cellPos.Y < cMax.Y
and cellPos.Z < cMax.Z
then
if waterForceTag then
cluster:SetWaterCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
Enum.WaterForce[waterForceTag.Value],
Enum.WaterDirection[waterForceDirectionTag.Value]
)
elseif not clusterValues then
cluster:SetCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
cellInfo.Material,
cellInfo.clusterType,
gInitial90DegreeRotations % 4
)
elseif clusterValues:IsA "Vector3Value" then
cluster:SetCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
clusterValues.Value.X,
clusterValues.Value.Y,
clusterValues.Value.Z
)
else
cluster:SetCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
clusterValues.Value,
0,
0
)
end
local autoWedgeClusterParts = false
if stampData.CurrentParts:FindFirstChild "AutoWedge" then
autoWedgeClusterParts = true
end
-- auto-wedge it
if autoWedgeClusterParts then
game.Workspace.Terrain:AutowedgeCells(
Region3int16.new(
Vector3int16.new(
cellPos.x - 1,
cellPos.y - 1,
cellPos.z - 1
),
Vector3int16.new(
cellPos.x + 1,
cellPos.y + 1,
cellPos.z + 1
)
)
)
end
-- kill the ghost part
stampData.CurrentParts.Parent = nil
-- Mark for undo. It has to happen here or the selection display will come back also.
pcall(function()
ChangeHistoryService:SetWaypoint "StamperSingle"
end)
return true
end
else
-- you tried to stamp a HSL-single part where one does not belong!
flashRedBox()
return false
end
end
local function getPlayer()
if game:FindFirstChild "Players" then
if game.Players.LocalPlayer then
return game.Players.LocalPlayer
end
end
return nil
end
-- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints
if
stampData.CurrentParts:IsA "Model"
or stampData.CurrentParts:IsA "Tool"
then
if stampData.CurrentParts:IsA "Model" then
-- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds... just don't ask X<
local manualWeldTable = {}
local manualWeldParentTable = {}
saveTheWelds(
stampData.CurrentParts,
manualWeldTable,
manualWeldParentTable
)
stampData.CurrentParts:BreakJoints()
stampData.CurrentParts:MakeJoints()
restoreTheWelds(manualWeldTable, manualWeldParentTable)
end
-- if it's a model, we also want to fill in the playerID and playerName tags, if it has those (e.g. for the friend-only door)
playerIdTag = stampData.CurrentParts:FindFirstChild "PlayerIdTag"
playerNameTag =
stampData.CurrentParts:FindFirstChild "PlayerNameTag"
if playerIdTag ~= nil then
tempPlayerValue = getPlayer()
if tempPlayerValue ~= nil then
playerIdTag.Value = tempPlayerValue.userId
end
end
if playerNameTag ~= nil then
if
game:FindFirstChild "Players"
and game.Players.LocalPlayer
then
tempPlayerValue = game.Players.LocalPlayer
if tempPlayerValue ~= nil then
playerNameTag.Value = tempPlayerValue.Name
end
end
end
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if stampData.CurrentParts:FindFirstChild "RobloxModel" == nil then
local stringTag = Instance.new "BoolValue"
stringTag.Name = "RobloxModel"
stringTag.Parent = stampData.CurrentParts
if
stampData.CurrentParts:FindFirstChild "RobloxStamper" == nil
then
local stringTag2 = Instance.new "BoolValue"
stringTag2.Name = "RobloxStamper"
stringTag2.Parent = stampData.CurrentParts
end
end
else
stampData.CurrentParts:BreakJoints()
if stampData.CurrentParts:FindFirstChild "RobloxStamper" == nil then
local stringTag2 = Instance.new "BoolValue"
stringTag2.Name = "RobloxStamper"
stringTag2.Parent = stampData.CurrentParts
end
end
-- make sure all the joints are activated before restoring anchor states
if not createJoints then
game.JointsService:CreateJoinAfterMoveJoints()
end
-- Restore the original properties for all parts being stamped
for part, transparency in pairs(stampData.TransparencyTable) do
part.Transparency = transparency
end
for part, archivable in pairs(stampData.ArchivableTable) do
part.Archivable = archivable
end
for part, material in pairs(stampData.MaterialTable) do
part.Material = material
end
for part, collide in pairs(stampData.CanCollideTable) do
part.CanCollide = collide
end
for part, anchored in pairs(stampData.AnchoredTable) do
part.Anchored = anchored
end
for decal, transparency in pairs(stampData.DecalTransparencyTable) do
decal.Transparency = transparency
end
for part, surfaces in pairs(stampData.SurfaceTypeTable) do
loadSurfaceTypes(part, surfaces)
end
if isMegaClusterPart() then
stampData.CurrentParts.Transparency = 0
end
-- re-enable all seats
setSeatEnabledStatus(stampData.CurrentParts, true)
stampData.TransparencyTable = nil
stampData.ArchivableTable = nil
stampData.MaterialTable = nil
stampData.CanCollideTable = nil
stampData.AnchoredTable = nil
stampData.SurfaceTypeTable = nil
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if stampData.CurrentParts:FindFirstChild "RobloxModel" == nil then
local stringTag = Instance.new "BoolValue"
stringTag.Name = "RobloxModel"
stringTag.Parent = stampData.CurrentParts
end
-- and make sure we don't delete it, now that it's not a ghost part
if ghostRemovalScript then
ghostRemovalScript.Parent = nil
end
--Re-enable the scripts
for _, script in pairs(stampData.DisabledScripts) do
script.Disabled = false
end
--Now that they are all marked enabled, reinsert them into the world so they start running
for _, script in pairs(stampData.DisabledScripts) do
local oldParent = script.Parent
script.Parent = nil
script:Clone().Parent = oldParent
end
-- clear out more data
stampData.DisabledScripts = nil
stampData.Dragger = nil
stampData.CurrentParts = nil
pcall(function()
ChangeHistoryService:SetWaypoint "StampedObject"
end)
return true
end
local function pauseStamper()
for i = 1, #mouseCons do -- stop the mouse from doing anything
mouseCons[i]:disconnect()
mouseCons[i] = nil
end
mouseCons = {}
if stampData and stampData.CurrentParts then -- remove our ghost part
stampData.CurrentParts.Parent = nil
stampData.CurrentParts:Remove()
end
resetHighScalabilityLine()
game.JointsService:ClearJoinAfterMoveJoints()
end
local function prepareUnjoinableSurfaces(modelCFrame, parts, whichSurface)
local AXIS_VECTORS =
{ Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1) } -- maybe last one is negative? TODO: check this!
local isPositive = 1
if whichSurface < 0 then
isPositive *= -1
whichSurface *= -1
end
local surfaceNormal = isPositive
* modelCFrame:vectorToWorldSpace(AXIS_VECTORS[whichSurface])
for i = 1, #parts do
local currPart = parts[i]
-- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable
local surfaceNormalInLocalCoords =
currPart.CFrame:vectorToObjectSpace(surfaceNormal)
if
math.abs(surfaceNormalInLocalCoords.X)
> math.abs(surfaceNormalInLocalCoords.Y)
then
if
math.abs(surfaceNormalInLocalCoords.X)
> math.abs(surfaceNormalInLocalCoords.Z)
then
if surfaceNormalInLocalCoords.X > 0 then
currPart.RightSurface = "Unjoinable"
else
currPart.LeftSurface = "Unjoinable"
end
else
if surfaceNormalInLocalCoords.Z > 0 then
currPart.BackSurface = "Unjoinable"
else
currPart.FrontSurface = "Unjoinable"
end
end
else
if
math.abs(surfaceNormalInLocalCoords.Y)
> math.abs(surfaceNormalInLocalCoords.Z)
then
if surfaceNormalInLocalCoords.Y > 0 then
currPart.TopSurface = "Unjoinable"
else
currPart.BottomSurface = "Unjoinable"
end
else
if surfaceNormalInLocalCoords.Z > 0 then
currPart.BackSurface = "Unjoinable"
else
currPart.FrontSurface = "Unjoinable"
end
end
end
end
end
local function resumeStamper()
clone, parts = prepareModel(modelToStamp)
if not clone or not parts then
return
end
-- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable
local unjoinableTag = clone:FindFirstChild("UnjoinableFaces", true)
if unjoinableTag then
for unjoinableSurface in string.gmatch(unjoinableTag.Value, "[^,]*") do
if tonumber(unjoinableSurface) then
if clone:IsA "Model" then
prepareUnjoinableSurfaces(
clone:GetModelCFrame(),
parts,
tonumber(unjoinableSurface)
)
else
prepareUnjoinableSurfaces(
clone.CFrame,
parts,
tonumber(unjoinableSurface)
)
end
end
end
end
stampData.ErrorBox = errorBox
if stampInModel then
clone.Parent = stampInModel
else
clone.Parent = game.Workspace
end
if clone:FindFirstChild("ClusterMaterial", true) then -- extract all info from vector
clusterMaterial = clone:FindFirstChild("ClusterMaterial", true)
if clusterMaterial:IsA "Vector3Value" then
cellInfo.Material = clusterMaterial.Value.X
cellInfo.clusterType = clusterMaterial.Value.Y
cellInfo.clusterOrientation = clusterMaterial.Value.Z
elseif clusterMaterial:IsA "IntValue" then
cellInfo.Material = clusterMaterial.Value
end
end
pcall(function()
mouseTarget = Mouse.Target
end)
if
mouseTarget
and mouseTarget.Parent:FindFirstChild "RobloxModel" == nil
then
game.JointsService:SetJoinAfterMoveTarget(mouseTarget)
else
game.JointsService:SetJoinAfterMoveTarget(nil)
end
game.JointsService:ShowPermissibleJoints()
for _, object in pairs(stampData.DisabledScripts) do
if object.Name == "GhostRemovalScript" then
object.Parent = stampData.CurrentParts
end
end
stampData.Dragger = Instance.new "Dragger"
--Begin a movement by faking a MouseDown signal
stampData.Dragger:MouseDown(parts[1], Vector3.new(0, 0, 0), parts)
stampData.Dragger:MouseUp()
DoStamperMouseMove(Mouse)
table.insert(
mouseCons,
Mouse.Move:connect(function()
if movingLock or stampUpLock then
return
end
movingLock = true
DoStamperMouseMove(Mouse)
movingLock = false
end)
)
table.insert(
mouseCons,
Mouse.Button1Down:connect(function()
DoStamperMouseDown(Mouse)
end)
)
table.insert(
mouseCons,
Mouse.Button1Up:connect(function()
stampUpLock = true
while movingLock do
wait()
end
stamped.Value = DoStamperMouseUp(Mouse)
resetHighScalabilityLine()
stampUpLock = false
end)
)
stamped.Value = false
end
local function resetStamperState(newModelToStamp)
-- if we have a new model, swap it out
if newModelToStamp then
if
not newModelToStamp:IsA "Model"
and not newModelToStamp:IsA "BasePart"
then
error "resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!"
end
modelToStamp = newModelToStamp
end
-- first clear our state
pauseStamper()
-- now lets load in the new model
resumeStamper()
end
-- load the model initially
resetStamperState()
-- setup the control table we pass back to the user
control.Stamped = stamped -- BoolValue that fires when user stamps
control.Paused = false
control.LoadNewModel = function(newStampModel) -- allows us to specify a new stamper model to be used with this stamper
if
newStampModel
and not newStampModel:IsA "Model"
and not newStampModel:IsA "BasePart"
then
error "Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!"
return nil
end
resetStamperState(newStampModel)
end
control.ReloadModel = function() -- will automatically set stamper to get a new model of current model and start stamping with new model
resetStamperState()
end
control.Pause = function() -- temporarily stops stamping, use resume to start up again
if not control.Paused then
pauseStamper()
control.Paused = true
else
print "RbxStamper Warning: Tried to call Control.Pause() when already paused"
end
end
control.Resume = function() -- resumes stamping, if currently paused
if control.Paused then
resumeStamper()
control.Paused = false
else
print "RbxStamper Warning: Tried to call Control.Resume() without Pausing First"
end
end
control.ResetRotation = function() -- resets the model rotation so new models are at default orientation
-- gInitial90DegreeRotations = 0
-- Note: This function will not always work quite the way we want it to; we will have to build this out further so it works with
-- High-Scalability and with the new model orientation setting methods (model:ResetOrientationToIdentity()) [HotThoth]
end
control.Destroy = function() -- Stops current Stamp operation and destroys control construct
for i = 1, #mouseCons do
mouseCons[i]:disconnect()
mouseCons[i] = nil
end
if keyCon then
keyCon:disconnect()
end
game.JointsService:ClearJoinAfterMoveJoints()
if adorn then
adorn:Destroy()
end
if adornPart then
adornPart:Destroy()
end
if errorBox then
errorBox:Destroy()
end
if stampData then
if stampData.Dragger then
stampData.Dragger:Destroy()
end
if stampData.CurrentParts then
stampData.CurrentParts:Destroy()
end
end
if control and control.Stamped then
control.Stamped:Destroy()
end
control = nil
end
return control
end
t.Help = function(funcNameOrFunc)
--input argument can be a string or a function. Should return a description (of arguments and expected side effects)
if
funcNameOrFunc == "GetStampModel"
or funcNameOrFunc == t.GetStampModel
then
return "Function GetStampModel. Arguments: assetId, useAssetVersionId. assetId is the asset to load in, define useAssetVersionId as true if assetId is a version id instead of a relative assetId. Side effect: returns a model of the assetId, or a string with error message if something fails"
end
if
funcNameOrFunc == "SetupStamperDragger"
or funcNameOrFunc == t.SetupStamperDragger
then
return "Function SetupStamperDragger. Side Effect: Creates 4x4 stamping mechanism for building out parts quickly. Arguments: ModelToStamp, Mouse, LegalStampCheckFunction. ModelToStamp should be a Model or Part, preferrably loaded from RbxStamper.GetStampModel and should have extents that are multiples of 4. Mouse should be a mouse object (obtained from things such as Tool.OnEquipped), used to drag parts around 'stamp' them out. LegalStampCheckFunction is optional, used as a callback with a table argument (table is full of instances about to be stamped). Function should return either true or false, false stopping the stamp action."
end
end
return t