2013/yue/73157242.yue

2394 lines
73 KiB
Plaintext

import "macros" as { $ }
$load $FILE
t = {}
-- waitForChild = (instance, name) ->
-- while not instance\FindFirstChild(name)
-- 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.
PlaneIntersection = (vectorPos) ->
hit = false
currCamera = game.Workspace.CurrentCamera
local startPos
with currCamera.CoordinateFrame.p
startPos = Vector3.new .X, .Y, .Z
endPos = Vector3.new vectorPos.X, vectorPos.Y, vectorPos.Z
normal = Vector3.new(0, 1, 0)
p3 = Vector3.new 0, 0, 0
startEndDot = normal\Dot(endPos - startPos)
cellPos = vectorPos
if startEndDot ~= 0
t = normal\Dot(p3 - startPos) / startEndDot
if t >= 0 and t <= 1
intersection = ((endPos - startPos) * t) + startPos
cellPos = game.Workspace.Terrain\WorldToCell(intersection)
hit = true
cellPos, hit
-- 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.
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.
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
cell = nil
planeLoc, hit = PlaneIntersection Vector3.new mouse.hit.x, mouse.hit.y, mouse.hit.z
if hit
cell = planeLoc
cell
-- setup helper functions
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
rotatePartAndChildren = (part, rotCF, offsetFromOrigin) ->
-- rotate this thing, if it's a part
if part\IsA "BasePart"
part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin)) + offsetFromOrigin
-- recursively do the same to all children
partChildren = part\GetChildren!
for c = 1, #partChildren
rotatePartAndChildren partChildren[c], rotCF, offsetFromOrigin
modelRotate = (model, yAngle) ->
rotCF = CFrame.Angles 0, yAngle, 0
offsetFromOrigin = model\GetModelCFrame!.p
rotatePartAndChildren model, rotCF, offsetFromOrigin
collectParts = (object, baseParts, scripts, decals) ->
if object\IsA "BasePart"
baseParts[] = object
elseif object\IsA "Script"
scripts[] = object
elseif object\IsA "Decal"
decals[] = object
for _, child in pairs object\GetChildren!
collectParts child, baseParts, scripts, decals
clusterPartsInRegion = (startVector, endVector) ->
cluster = game.Workspace\FindFirstChild "Terrain"
startCell = cluster\WorldToCell startVector
endCell = cluster\WorldToCell endVector
startX = startCell.X
startY = startCell.Y
startZ = startCell.Z
endX = endCell.X
endY = endCell.Y
endZ = endCell.Z
if startX < cluster.MaxExtents.Min.X
startX = cluster.MaxExtents.Min.X
if startY < cluster.MaxExtents.Min.Y
startY = cluster.MaxExtents.Min.Y
if startZ < cluster.MaxExtents.Min.Z
startZ = cluster.MaxExtents.Min.Z
if endX > cluster.MaxExtents.Max.X
endX = cluster.MaxExtents.Max.X
if endY > cluster.MaxExtents.Max.Y
endY = cluster.MaxExtents.Max.Y
if endZ > cluster.MaxExtents.Max.Z
endZ = cluster.MaxExtents.Max.Z
for x = startX, endX
for y = startY, endY
for z = startZ, endZ
if cluster\GetCell(x, y, z).Value > 0
return true
false
findSeatsInModel = (parent, seatTable) ->
return if not parent
if parent.className == "Seat" or parent.className == "VehicleSeat"
table.insert seatTable, parent
myChildren = parent\GetChildren!
for j = 1, #myChildren
findSeatsInModel myChildren[j], seatTable
setSeatEnabledStatus = (model, isEnabled) ->
seatList = {}
findSeatsInModel model, seatList
if isEnabled
-- remove any welds called "SeatWeld" in seats
for i = 1, #seatList
nextSeat = seatList[i]\FindFirstChild "SeatWeld"
while nextSeat
nextSeat\Remove!
nextSeat = seatList[i]\FindFirstChild "SeatWeld"
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
fakeWeld = Instance.new "Weld"
fakeWeld.Name = "SeatWeld"
fakeWeld.Parent = seatList[i]
autoAlignToFace = (parts) ->
aatf = parts\FindFirstChild "AutoAlignToFace"
if aatf
aatf.Value
else
false
getClosestAlignedWorldDirection = (aVector3InWorld) ->
xDir = Vector3.new 1, 0, 0
yDir = Vector3.new 0, 1, 0
zDir = Vector3.new 0, 0, 1
xDot = aVector3InWorld.x * xDir.x + aVector3InWorld.y * xDir.y + aVector3InWorld.z * xDir.z
yDot = aVector3InWorld.x * yDir.x + aVector3InWorld.y * yDir.y + aVector3InWorld.z * yDir.z
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
if xDot > 0
0
else
3
elseif math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs zDot
if yDot > 0
1
else
4
else
if zDot > 0
2
else
5
positionPartsAtCFrame3 = (aCFrame, currentParts) ->
local insertCFrame
if not currentParts
return currentParts
if currentParts and (currentParts\IsA"Model" or currentParts\IsA "Tool")
insertCFrame = currentParts\GetModelCFrame!
currentParts\TranslateBy aCFrame.p - insertCFrame.p
else
currentParts.CFrame = aCFrame
currentParts
calcRayHitTime = (rayStart, raySlope, intersectionPlane) ->
if math.abs(raySlope) < 0.01
return 0
-- 0 slope --> we just say intersection time is 0, and sidestep this dimension
(intersectionPlane - rayStart) / raySlope
modelTargetSurface = (partOrModel, rayStart, rayEnd) ->
if not partOrModel
return 0
local modelCFrame, modelSize
if partOrModel\IsA "Model"
modelCFrame = partOrModel\GetModelCFrame!
modelSize = partOrModel\GetModelSize!
else
modelCFrame = partOrModel.CFrame
modelSize = partOrModel.Size
mouseRayStart = modelCFrame\pointToObjectSpace rayStart
mouseRayEnd = modelCFrame\pointToObjectSpace rayEnd
mouseSlope = mouseRayEnd - mouseRayStart
xPositive = 1
yPositive = 1
zPositive = 1
if mouseSlope.X > 0
xPositive = -1
if mouseSlope.Y > 0
yPositive = -1
if mouseSlope.Z > 0
zPositive = -1
-- find which surface the transformed mouse ray hits (using modelSize):
xHitTime = calcRayHitTime mouseRayStart.X, mouseSlope.X, modelSize.X / 2 * xPositive
yHitTime = calcRayHitTime mouseRayStart.Y, mouseSlope.Y, modelSize.Y / 2 * yPositive
zHitTime = calcRayHitTime mouseRayStart.Z, mouseSlope.Z, modelSize.Z / 2 * zPositive
hitFace = 0
--if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0
if xHitTime > yHitTime
if xHitTime > zHitTime
-- xFace is hit
hitFace = 1 * xPositive
else
-- zFace is hit
hitFace = 3 * zPositive
else
if yHitTime > zHitTime
-- yFace is hit
hitFace = 2 * yPositive
else
-- zFace is hit
hitFace = 3 * zPositive
hitFace
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.
minVec = Vector3.new math.huge, math.huge, math.huge
maxVec = Vector3.new -math.huge, -math.huge, -math.huge
if partOrModel\IsA "Terrain"
minVec = Vector3.new -2, -2, -2
maxVec = Vector3.new 2, 2, 2
elseif partOrModel\IsA "BasePart"
minVec = -0.5 * partOrModel.Size
maxVec = -minVec
else
maxVec = partOrModel\GetModelSize! * 0.5
minVec = -maxVec
-- Adjust bounding box to reflect what the model or part author wants in terms of justification
justifyValue = partOrModel\FindFirstChild "Justification"
if justifyValue?
-- find the multiple of 4 that contains the model
justify = justifyValue.Value
two = Vector3.new(2, 2, 2)
actualBox = maxVec - minVec - Vector3.new 0.01, 0.01, 0.01
containingGridBox = Vector3.new(
4 * math.ceil(actualBox.x / 4),
4 * math.ceil(actualBox.y / 4),
4 * math.ceil actualBox.z / 4
)
adjustment = containingGridBox - actualBox
minVec -= 0.5 * adjustment * justify
maxVec += 0.5 * adjustment * (two - justify)
minVec, maxVec
getBoundingBoxInWorldCoordinates = (partOrModel) ->
minVec = Vector3.new math.huge, math.huge, math.huge
maxVec = Vector3.new -math.huge, -math.huge, -math.huge
if partOrModel\IsA"BasePart" and not partOrModel\IsA "Terrain"
vec1 = partOrModel.CFrame\pointToWorldSpace -0.5 * partOrModel.Size
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"
-- we shouldn't have to deal with this case
--minVec = Vector3.new(-2, -2, -2)
--maxVec = Vector3.new(2, 2, 2)
-- else
vec1 = partOrModel\GetModelCFrame!\pointToWorldSpace -0.5 * partOrModel\GetModelSize!
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
minVec, maxVec
getTargetPartBoundingBox = (targetPart) ->
getBoundingBox2 if targetPart.Parent\FindFirstChild"RobloxModel"?
targetPart.Parent
else
targetPart
getMouseTargetCFrame = (targetPart) -> with targetPart
return if .Parent\FindFirstChild"RobloxModel"?
if .Parent\IsA "Tool"
.Parent.Handle.CFrame
else
.Parent\GetModelCFrame!
else
.CFrame
isBlocker = (part) -> -- returns whether or not we want to cancel the stamp because we're blocked by this part
if not part
return false
if not part.Parent
return false
if part\FindFirstChild "Humanoid"
return false
if part\FindFirstChild "RobloxStamper" or part\FindFirstChild "RobloxModel"
return true
if part\IsA"Part" and not part.CanCollide
return false
if part == game.Lighting
return false
isBlocker part.Parent
-- 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
spaceAboveCharacter = (charTorso, newTorsoY, stampData) ->
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
if partsAboveChar[j].CanCollide and not partsAboveChar[j]\IsDescendantOf stampData.CurrentParts
return false
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)
)
return false
true
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.
return if not Mouse
-- This can happen sometimes, return if so
return if not stampData
error "findConfigAtMouseTarget: stampData is nil"
return if not stampData["CurrentParts"]
grid = 4.0
admissibleConfig = false
targetConfig = CFrame.new 0, 0, 0
minBB, maxBB = getBoundingBox2 stampData.CurrentParts
diagBB = maxBB - minBB
local insertCFrame
if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool"
insertCFrame = stampData.CurrentParts\GetModelCFrame!
else
insertCFrame = stampData.CurrentParts.CFrame
if Mouse
if stampData.CurrentParts\IsA "Tool"
Mouse.TargetFilter = stampData.CurrentParts.Handle
else
Mouse.TargetFilter = stampData.CurrentParts
hitPlane = false
local targetPart
success = try
targetPart = Mouse.Target
if not success -- or targetPart == nil
return admissibleConfig, targetConfig
mouseHitInWorld = Vector3.new 0, 0, 0
if Mouse
mouseHitInWorld = Vector3.new Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z
local cellPos
-- Nothing was hit, so check for the default plane.
if nil == targetPart
cellPos = GetTerrainForMouse Mouse
if nil == cellPos
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)
-- test mouse hit location
local minBBTarget, maxBBTarget = getTargetPartBoundingBox targetPart
diagBBTarget = maxBBTarget - minBBTarget
targetCFrame = getMouseTargetCFrame targetPart
if targetPart\IsA "Terrain"
if not cluster
global cluster = game.Workspace\FindFirstChild "Terrain"
cellID = cluster\WorldToCellPreferSolid mouseHitInWorld
if hitPlane
cellID = cellPos
targetCFrame = CFrame.new game.Workspace.Terrain\CellCenterToWorld cellID.x, cellID.y, cellID.z
mouseHitInTarget = targetCFrame\pointToObjectSpace mouseHitInWorld
targetVectorInWorld = Vector3.new 0, 0, 0
if Mouse
-- 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")
hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- best, if you get it right
WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
if hitFace > 0
targetVectorInWorld = targetCFrame\vectorToWorldSpace(WORLD_AXES[hitFace])
elseif hitFace < 0
targetVectorInWorld = targetCFrame\vectorToWorldSpace(-WORLD_AXES[-hitFace])
end]]
local targetRefPointInTarget, insertRefPointInInsert
local clampToSurface
if getClosestAlignedWorldDirection(targetVectorInWorld) == 0
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
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
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
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
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
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
delta = mouseHitInTarget - targetRefPointInTarget
deltaClamped = Vector3.new(
grid * math.modf(delta.x / grid),
grid * math.modf(delta.y / grid),
grid * math.modf delta.z / grid
)
deltaClamped *= clampToSurface
targetTouchInTarget = deltaClamped + targetRefPointInTarget
TargetTouchRelToWorld = targetCFrame\pointToWorldSpace targetTouchInTarget
InsertTouchInWorld = insertCFrame\vectorToWorldSpace insertRefPointInInsert
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
admissibleConfig, targetConfig, getClosestAlignedWorldDirection(targetVectorInWorld)
truncateToCircleEighth = (bigValue, littleValue) ->
big = math.abs bigValue
little = math.abs littleValue
hypotenuse = math.sqrt big * big + little * little
frac = little / hypotenuse
bigSign = 1
littleSign = 1
if bigValue < 0
bigSign = -1
if littleValue < 0
littleSign = -1
if frac > 0.382683432
-- 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
saveTheWelds = (object, manualWeldTable, manualWeldParentTable) ->
if object\IsA"ManualWeld" or object\IsA "Rotate"
table.insert manualWeldTable, object
table.insert manualWeldParentTable, object.Parent
else
children = object\GetChildren!
for i = 1, #children
saveTheWelds children[i], manualWeldTable, manualWeldParentTable
restoreTheWelds = (manualWeldTable, manualWeldParentTable) ->
for i = 1, #manualWeldTable
manualWeldTable[i].Parent = manualWeldParentTable[i]
t.CanEditRegion = (partOrModel, EditRegion) -> -- todo: use model and stamper metadata
if not EditRegion
return true, false
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
return false, false
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
return false, false
true, false
t.GetStampModel = (assetId, terrainShape, useAssetVersionId) ->
if assetId == 0
return nil, "No Asset"
if assetId < 0
return nil, "Negative Asset"
UnlockInstances = (object) ->
if object\IsA "BasePart"
object.Locked = false
for _, child in pairs object\GetChildren!
UnlockInstances child
getClosestColorToTerrainMaterial = (terrainValue) ->
BrickColor.new switch terrainValue
when 1 then "Bright green"
when 2 then "Bright yellow"
when 3 then "Bright red"
when 4 then "Sand red"
when 5 then "Black"
when 6 then "Dark stone grey"
when 7 then "Sand blue"
when 8 then "Deep orange"
when 9 then "Dark orange"
when 10 then "Reddish brown"
when 11 then "Light orange"
when 12 then "Light stone grey"
when 13 then "Sand green"
when 14 then "Medium stone grey"
when 15 then "Really red"
when 16 then "Really blue"
when 17 then "Bright blue"
else "Bright green"
setupFakeTerrainPart = (cellMat, cellType, cellOrient) ->
local newTerrainPiece
if cellType == 1 or cellType == 4
newTerrainPiece = Instance.new "WedgePart"
newTerrainPiece.formFactor = "Custom"
elseif cellType == 2
newTerrainPiece = Instance.new "CornerWedgePart"
else
newTerrainPiece = Instance.new "Part"
newTerrainPiece.formFactor = "Custom"
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
sideways = 0
flipped = math.pi
if cellType == 4
sideways = -math.pi / 2
if cellType == 2 or cellType == 3
flipped = 0
newTerrainPiece.CFrame = CFrame.Angles 0, math.pi / 2 * cellOrient + flipped, sideways
if cellType == 3
inverseCornerWedgeMesh = Instance.new "SpecialMesh"
inverseCornerWedgeMesh.MeshType = "FileMesh"
inverseCornerWedgeMesh.MeshId = "http://www.roblox.com/asset?id=66832495"
inverseCornerWedgeMesh.Scale = Vector3.new 2, 2, 2
inverseCornerWedgeMesh.Parent = newTerrainPiece
materialTag = Instance.new "Vector3Value"
materialTag.Value = Vector3.new cellMat, cellType, cellOrient
materialTag.Name = "ClusterMaterial"
materialTag.Parent = newTerrainPiece
return newTerrainPiece
-- 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
loading = true
if useAssetVersionId
loader = coroutine.create ->
root = game\GetService"InsertService"\LoadAssetVersion assetId
loading = false
coroutine.resume loader
else
loader = coroutine.create ->
root = game\GetService"InsertService"\LoadAsset assetId
loading = false
coroutine.resume loader
lastGameTime = 0
totalTime = 0
maxWait = 8
while loading and totalTime < maxWait
lastGameTime = tick!
wait 1
totalTime += tick! - lastGameTime
loading = false
if totalTime >= maxWait
return nil, "Load Time Fail"
if root == nil
return nil, "Load Asset Fail"
if not root\IsA "Model"
return nil, "Load Type Fail"
instances = root\GetChildren!
if #instances == 0
return nil, "Empty Model Fail"
--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
if instance\IsA "Team"
instance.Parent = game\GetService "Teams"
elseif instance\IsA "Sky"
lightingService = game\GetService "Lighting"
for _, child in pairs lightingService\GetChildren!
if child\IsA "Sky"
child\Remove!
instance.Parent = lightingService
return
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if not root\FindFirstChild"RobloxModel"?
stringTag = Instance.new "BoolValue"
stringTag.Name = "RobloxModel"
stringTag.Parent = root
if not root\FindFirstChild"RobloxStamper"?
stringTag2 = Instance.new "BoolValue"
stringTag2.Name = "RobloxStamper"
stringTag2.Parent = root
if terrainShape
if root.Name == "MegaClusterCube"
if terrainShape == 6 -- insert an autowedging tag
autowedgeTag = Instance.new "BoolValue"
autowedgeTag.Name = "AutoWedge"
autowedgeTag.Parent = root
else
clusterTag = root\FindFirstChild "ClusterMaterial"
if clusterTag
if clusterTag\IsA "Vector3Value"
root = setupFakeTerrainPart(clusterTag.Value.X, terrainShape, clusterTag.Value.Z)
else
root = setupFakeTerrainPart(clusterTag.Value, terrainShape, 0)
else
root = setupFakeTerrainPart(1, terrainShape, 0)
return root
t.SetupStamperDragger = (modelToStamp, Mouse, StampInModel, AllowedStampRegion, StampFailedFunc) ->
if not modelToStamp
error "SetupStamperDragger: modelToStamp (first arg) is nil! Should be a stamper model"
return nil
if not modelToStamp\IsA"Model" and not modelToStamp\IsA "BasePart"
error "SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!"
return nil
if not Mouse
error "SetupStamperDragger: Mouse (second arg) is nil! Should be a mouse object"
return nil
if not Mouse\IsA "Mouse"
error "SetupStamperDragger: Mouse (second arg) is not of type Mouse!"
return nil
local stampInModel
local allowedStampRegion
local stampFailedFunc
if StampInModel
if not StampInModel\IsA "Model"
error "SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'"
return nil
if not AllowedStampRegion
error "SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined"
return nil
stampFailedFunc = StampFailedFunc
stampInModel = StampInModel
allowedStampRegion = AllowedStampRegion
-- Init all state variables
gInitial90DegreeRotations = 0
local stampData
local mouseTarget
errorBox = Instance.new "SelectionBox"
errorBox.Color = BrickColor.new "Bright red"
errorBox.Transparency = 0
errorBox.Archivable = false
-- for megacluster MEGA STAMPING
adornPart = Instance.new "Part"
adornPart.Parent = nil
adornPart.formFactor = "Custom"
adornPart.Size = Vector3.new(4, 4, 4)
adornPart.CFrame = CFrame.new!
adornPart.Archivable = false
adorn = Instance.new "SelectionBox"
adorn.Color = BrickColor.new "Toothpaste"
adorn.Adornee = adornPart
adorn.Visible = true
adorn.Transparency = 0
adorn.Name = "HighScalabilityStamperLine"
adorn.Archivable = false
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
control = {}
movingLock = false
stampUpLock = false
unstampableSurface = false
mouseCons = {}
local keyCon
stamped = Instance.new "BoolValue"
stamped.Archivable = false
stamped.Value = false
lastTarget = {}
lastTarget.TerrainOrientation = 0
lastTarget.CFrame = 0
cellInfo = {}
cellInfo.Material = 1
cellInfo.clusterType = 0
cellInfo.clusterOrientation = 0
isMegaClusterPart = ->
if not stampData
return false
if not stampData.CurrentParts
return false
stampData.CurrentParts\FindFirstChild("ClusterMaterial", true) or
(stampData.CurrentParts.Name == "MegaClusterCube")
DoHighScalabilityRegionSelect = ->
megaCube = stampData.CurrentParts\FindFirstChild "MegaClusterCube"
if not megaCube
if not stampData.CurrentParts.Name == "MegaClusterCube"
return
else
megaCube = stampData.CurrentParts
HighScalabilityLine.End = megaCube.CFrame.p
local line
line2 = Vector3.new 0, 0, 0
line3 = Vector3.new 0, 0, 0
if HighScalabilityLine.Dimensions == 1
-- 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)
if math.abs(line.X) < math.abs(line.Z)
-- limit to Y/Z plane, domination unknown
local newY, newZ
if math.abs(line.Y) > math.abs(line.Z)
newY, newZ = truncateToCircleEighth(line.Y, line.Z)
else
newZ, newY = truncateToCircleEighth(line.Z, line.Y)
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)
else
if math.abs(line.Y) < math.abs(line.Z)
-- limit to X/Z plane, domination unknown
local newX, newZ
if math.abs(line.X) > math.abs(line.Z)
newX, newZ = truncateToCircleEighth(line.X, line.Z)
else
newZ, newX = truncateToCircleEighth(line.Z, line.X)
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)
HighScalabilityLine.InternalLine = line
elseif HighScalabilityLine.Dimensions == 2
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
yAxis = tempCFrame\vectorToWorldSpace(Vector3.new(0, 1, 0))
xAxis = tempCFrame\vectorToWorldSpace(Vector3.new(1, 0, 0))
xComp = xAxis\Dot(line2)
yComp = yAxis\Dot(line2)
if math.abs(yComp) > math.abs(xComp)
line2 -= xAxis * xComp
else
line2 -= yAxis * yComp
HighScalabilityLine.InternalLine = line2
elseif HighScalabilityLine.Dimensions == 3
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
-- resize the "line" graphic to be the correct size and orientation
tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)
if HighScalabilityLine.Dimensions == 1 -- 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
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
-- make player able to see this ish
local gui
if game.Players["LocalPlayer"]
gui = game.Players.LocalPlayer\FindFirstChild "PlayerGui"
if gui and gui\IsA "PlayerGui"
if (HighScalabilityLine.Dimensions == 1 and line.magnitude > 3) or
HighScalabilityLine.Dimensions > 1
-- don't show if mouse hasn't moved enough
HighScalabilityLine.Adorn.Parent = gui
if not gui? -- we are in studio
gui = game\GetService "CoreGui"
if (HighScalabilityLine.Dimensions == 1 and line.magnitude > 3) or HighScalabilityLine.Dimensions > 1 -- don't show if mouse hasn't moved enough
HighScalabilityLine.Adorn.Parent = gui
DoStamperMouseMove = (Mouse) ->
if not Mouse
error "Error: RbxStamper.DoStamperMouseMove: Mouse is nil"
return
if not Mouse\IsA "Mouse"
error("Error: RbxStamper.DoStamperMouseMove: Mouse is of type", Mouse.className, "should be of type Mouse")
return
-- There wasn't a target (no part or terrain), so check for plane intersection.
if not Mouse.Target
cellPos = GetTerrainForMouse(Mouse)
if nil == cellPos
return
if not stampData
return
-- 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
error "RbxStamper.DoStamperMouseMove No configFound, returning"
return
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 -- pre-rotate the flag or portrait so it's aligned correctly
if targetSurface == 3
numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 0
numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 5
numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
elseif targetSurface == 2
numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
ry = math.pi / 2
gInitial90DegreeRotations += numRotations
if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool"
--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
-- 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"
currModelCFrame = stampData.CurrentParts\GetModelCFrame!
else
currModelCFrame = stampData.CurrentParts.CFrame
minBB += targetCFrame.p - currModelCFrame.p
maxBB += targetCFrame.p - currModelCFrame.p
-- don't drag into terrain
if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector)
if lastTarget.CFrame
if stampData.CurrentParts\FindFirstChild("ClusterMaterial", true)
theClusterMaterial = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true)
if theClusterMaterial\IsA "Vector3Value"
stampClusterMaterial = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true)
if stampClusterMaterial
stampClusterMaterial = clusterMat
return
-- 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!
cellToStamp = game.Workspace.Terrain\WorldToCell targetCFrame.p
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
)
positionPartsAtCFrame3 targetCFrame, stampData.CurrentParts
lastTarget.CFrame = targetCFrame -- successful positioning, so update 'dat cframe
if stampData.CurrentParts\FindFirstChild "ClusterMaterial", true
clusterMat = stampData.CurrentParts\FindFirstChild "ClusterMaterial", true
if clusterMat\IsA "Vector3Value"
lastTarget.TerrainOrientation = clusterMat.Value.Z
-- auto break joints code
if Mouse and Mouse.Target and Mouse.Target.Parent
modelInfo = Mouse.Target\FindFirstChild "RobloxModel"
if not modelInfo
modelInfo = Mouse.Target.Parent\FindFirstChild "RobloxModel"
myModelInfo = stampData.CurrentParts\FindFirstChild "UnstampableFaces"
--if (modelInfo and modelInfo.Parent\FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then -- need better targetSurface calcs
if true
breakingFaces = ""
myBreakingFaces = ""
if modelInfo and modelInfo.Parent\FindFirstChild "UnstampableFaces"
breakingFaces = modelInfo.Parent.UnstampableFaces.Value
if myModelInfo
myBreakingFaces = myModelInfo.Value
hitFace = 0
if modelInfo
hitFace = modelTargetSurface(
modelInfo.Parent,
game.Workspace.CurrentCamera.CoordinateFrame.p,
Mouse.Hit.p
)
-- are we stamping TO an unstampable surface?
for bf in string.gmatch breakingFaces, "[^,]+"
if hitFace == tonumber bf
-- return before we hit the JointsService code below!
unstampableSurface = true
game.JointsService\ClearJoinAfterMoveJoints! -- clear the JointsService cache
return
-- 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, "[^,]+"
if hitFace == tonumber bf
unstampableSurface = true
game.JointsService\ClearJoinAfterMoveJoints! -- clear the JointsService cache
return
-- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame
-- targetSurface: 1 is top, 4 is bottom,
-- 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 try
if Mouse and Mouse.Target and not Mouse.Target.Parent\FindFirstChild"RobloxModel"?
return
else
return
-- ?
)
error "Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check"
game.JointsService\ClearJoinAfterMoveJoints!
Mouse = nil
return
if Mouse and Mouse.Target and not Mouse.Target.Parent\FindFirstChild"RobloxModel"?
game.JointsService\SetJoinAfterMoveTarget Mouse.Target
else
game.JointsService\SetJoinAfterMoveTarget nil
game.JointsService\ShowPermissibleJoints!
-- here we allow for a line of high-scalability parts
if isMegaClusterPart! and HighScalabilityLine and HighScalabilityLine.Start
DoHighScalabilityRegionSelect!
setupKeyListener = (key, Mouse) ->
return if control and control["Paused"]
-- don't do this if we have no stamp
key = string.lower(key)
if key == "r" and not autoAlignToFace(stampData.CurrentParts) -- rotate the model
gInitial90DegreeRotations += 1
-- Update orientation value if this is a fake terrain part
clusterValues = stampData.CurrentParts\FindFirstChild "ClusterMaterial", true
if clusterValues and clusterValues\IsA "Vector3Value"
clusterValues.Value = Vector3.new(
clusterValues.Value.X,
clusterValues.Value.Y,
(clusterValues.Value.Z + 1) % 4
)
-- Rotate the parts or all the parts in the model
ry = math.pi / 2
if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool"
--stampData.CurrentParts\Rotate(0, ry, 0)
modelRotate stampData.CurrentParts, ry
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
-- After rotating, update the position
configFound, targetCFrame = findConfigAtMouseTarget Mouse, stampData
if configFound
positionPartsAtCFrame3 targetCFrame, stampData.CurrentParts
-- update everything else in MouseMove
DoStamperMouseMove Mouse
elseif key == "c" -- try to expand our high scalability dragger dimension
if HighScalabilityLine.InternalLine and
HighScalabilityLine.InternalLine.magnitude > 0 and
HighScalabilityLine.Dimensions < 3
HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] = HighScalabilityLine.End
HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] = HighScalabilityLine.InternalLine
HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions + 1
HighScalabilityLine.NewHint = true
keyCon = Mouse.KeyDown\connect (key) -> -- init key connection (keeping code close to func)
setupKeyListener key, Mouse
resetHighScalabilityLine = ->
if HighScalabilityLine
HighScalabilityLine.Start = nil
HighScalabilityLine.End = nil
HighScalabilityLine.InternalLine = nil
HighScalabilityLine.NewHint = true
flashRedBox = ->
gui = game.CoreGui
if game\FindFirstChild "Players"
if game.Players["LocalPlayer"]
if game.Players.LocalPlayer\FindFirstChild "PlayerGui"
gui = game.Players.LocalPlayer.PlayerGui
return if not stampData["ErrorBox"]
stampData.ErrorBox.Parent = gui
if stampData.CurrentParts\IsA "Tool"
stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle
else
stampData.ErrorBox.Adornee = stampData.CurrentParts
delay 0, ->
for _ = 1, 3
if stampData["ErrorBox"]
stampData.ErrorBox.Visible = true
wait 0.13
if stampData["ErrorBox"]
stampData.ErrorBox.Visible = false
wait 0.13
if stampData["ErrorBox"]
stampData.ErrorBox.Adornee = nil
stampData.ErrorBox.Parent = Tool
DoStamperMouseDown = (Mouse) ->
if not Mouse
error "Error: RbxStamper.DoStamperMouseDown: Mouse is nil"
return
if not Mouse\IsA "Mouse"
error "Error: RbxStamper.DoStamperMouseDown: Mouse is of type", Mouse.className, "should be of type Mouse"
return
if not stampData
return
if isMegaClusterPart!
if Mouse and HighScalabilityLine
megaCube = stampData.CurrentParts\FindFirstChild("MegaClusterCube", true)
terrain = game.Workspace.Terrain
if megaCube
HighScalabilityLine.Dimensions = 1
tempCell = terrain\WorldToCell(megaCube.CFrame.p)
HighScalabilityLine.Start = terrain\CellCenterToWorld tempCell.X, tempCell.Y, tempCell.Z
return
else
HighScalabilityLine.Dimensions = 1
tempCell = terrain\WorldToCell(stampData.CurrentParts.CFrame.p)
HighScalabilityLine.Start = terrain\CellCenterToWorld tempCell.X, tempCell.Y, tempCell.Z
return
loadSurfaceTypes = (part, surfaces) ->
with part
.TopSurface = surfaces[1]
.BottomSurface = surfaces[2]
.LeftSurface = surfaces[3]
.RightSurface = surfaces[4]
.FrontSurface = surfaces[5]
.BackSurface = surfaces[6]
saveSurfaceTypes = (part, myTable) ->
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
-- makeSurfaceUnjoinable = (part, surface) ->
-- -- TODO: FILL OUT!
-- end
prepareModel = (model) ->
if not model
return nil
gDesiredTrans = 0.7
gStaticTrans = 1
clone = model\Clone!
scripts = {}
parts = {}
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
return nil, "no parts found in modelToStamp"
for _, script in pairs scripts
if not script.Disabled
script.Disabled = true
stampData.DisabledScripts[#stampData.DisabledScripts + 1] = script
for _, part in pairs parts
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)
fadeInDelayTime = 0.5
transFadeInTime = 0.5
delay 0, ->
wait fadeInDelayTime -- give it some time to be completely transparent
begTime = tick!
currTime = begTime
while (currTime - begTime) < transFadeInTime and
part and
part\IsA"BasePart" and
part.Transparency > gDesiredTrans
newTrans = 1 - (((currTime - begTime) / transFadeInTime) * (gStaticTrans - gDesiredTrans))
if stampData["TransparencyTable"] and stampData.TransparencyTable[part]
part.Transparency = newTrans + (1 - newTrans) * stampData.TransparencyTable[part]
wait 0.03
currTime = tick!
if part and part\IsA "BasePart"
if stampData["TransparencyTable"] and stampData.TransparencyTable[part]
part.Transparency = gDesiredTrans + (1 - gDesiredTrans) * stampData.TransparencyTable[part]
for _, decal in pairs decals
stampData.DecalTransparencyTable[decal] = decal.Transparency
decal.Transparency = gDesiredTrans + (1 - gDesiredTrans) * decal.Transparency
-- 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
stampData.CurrentParts\ResetOrientationToIdentity!
gInitial90DegreeRotations = 0
else -- pre-rotate if necessary
ry = gInitial90DegreeRotations * math.pi / 2
if stampData.CurrentParts\IsA"Model" or stampData.CurrentParts\IsA "Tool"
--stampData.CurrentParts\Rotate(0, ry, 0)
modelRotate stampData.CurrentParts, ry
else
stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
-- 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]
clusterMaterial = stampData.CurrentParts\FindFirstChild "ClusterMaterial", true
if clusterMaterial and clusterMaterial\IsA"Vector3Value"
clusterMaterial.Value = Vector3.new(
clusterMaterial.Value.X,
clusterMaterial.Value.Y,
(clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4
)
-- After rotating, update the position
local configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
if configFound
stampData.CurrentParts = positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
-- to show joints during the mouse move
game.JointsService\SetJoinAfterMoveInstance(stampData.CurrentParts)
return clone, parts
checkTerrainBlockCollisions = (cellPos, checkHighScalabilityStamp) ->
cellCenterToWorld = game.Workspace.Terrain.CellCenterToWorld
cellCenter = cellCenterToWorld game.Workspace.Terrain, cellPos.X, cellPos.Y, cellPos.Z
cellBlockingParts = game.Workspace\FindPartsInRegion3(
Region3.new(
cellCenter - Vector3.new(2, 2, 2) + insertBoundingBoxOverlapVector,
cellCenter + Vector3.new(2, 2, 2) - insertBoundingBoxOverlapVector
),
stampData.CurrentParts,
100
)
skipThisCell = false
for b = 1, #cellBlockingParts
if isBlocker cellBlockingParts[b]
skipThisCell = true
break
if not skipThisCell
-- pop players up above any set cells
alreadyPushedUp = {}
-- if no blocking model below, then see if stamping on top of a character
for b = 1, #cellBlockingParts
if cellBlockingParts[b].Parent and
not alreadyPushedUp[cellBlockingParts[b].Parent] and
cellBlockingParts[b].Parent\FindFirstChild "Humanoid" and
cellBlockingParts[b].Parent\FindFirstChild"Humanoid"\IsA "Humanoid"
-----------------------------------------------------------------------------------
blockingPersonTorso = cellBlockingParts[b].Parent\FindFirstChild "Torso"
alreadyPushedUp[cellBlockingParts[b].Parent] = true
if blockingPersonTorso
-- 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)
newY = cellCenter.Y + 5
if spaceAboveCharacter blockingPersonTorso, newY, stampData
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
-----------------------------------------------------------------------------------
if not skipThisCell -- if we STILL aren't skipping... then we're good to go!
canSetCell = true
if checkHighScalabilityStamp -- check to see if cell is in region, if not we'll skip set
if allowedStampRegion
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)
canSetCell = false
return canSetCell
false
ResolveMegaClusterStamp = (checkHighScalabilityStamp) ->
cellSet = false
cluster = game.Workspace.Terrain
line = HighScalabilityLine.InternalLine
cMax = game.Workspace.Terrain.MaxExtents.Max
cMin = game.Workspace.Terrain.MaxExtents.Min
clusterMaterial = 1 -- default is grass
clusterType = 0 -- default is brick
clusterOrientation = 0 -- default is 0 rotation
autoWedgeClusterParts = false
if stampData.CurrentParts\FindFirstChild "AutoWedge"
autoWedgeClusterParts = true
if stampData.CurrentParts\FindFirstChild("ClusterMaterial", true)
clusterMaterial = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true)
if clusterMaterial\IsA "Vector3Value"
clusterType = clusterMaterial.Value.Y
clusterOrientation = clusterMaterial.Value.Z
clusterMaterial = clusterMaterial.Value.X
elseif clusterMaterial\IsA "IntValue"
clusterMaterial = clusterMaterial.Value
if HighScalabilityLine.Adorn.Parent and
HighScalabilityLine.Start and
((HighScalabilityLine.Dimensions > 1) or (line and line.magnitude > 0))
startCell = game.Workspace.Terrain\WorldToCell(HighScalabilityLine.Start)
xInc = { 0, 0, 0 }
yInc = { 0, 0, 0 }
zInc = { 0, 0, 0 }
incrementVect = { nil, nil, nil }
stepVect =
* Vector3.new 0, 0, 0
* Vector3.new 0, 0, 0
* Vector3.new 0, 0, 0
worldAxes =
* Vector3.new 1, 0, 0
* Vector3.new 0, 1, 0
* Vector3.new 0, 0, 1
lines = {}
if HighScalabilityLine.Dimensions > 1
table.insert(lines, HighScalabilityLine.MoreLines[1])
if line and line.magnitude > 0
table.insert(lines, line)
if HighScalabilityLine.Dimensions > 2
table.insert(lines, HighScalabilityLine.MoreLines[2])
for i = 1, #lines
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
xInc[i] = 1
elseif lines[i].X < 0
xInc[i] = -1
if lines[i].Y > 0
yInc[i] = 1
elseif lines[i].Y < 0
yInc[i] = -1
if lines[i].Z > 0
zInc[i] = 1
elseif lines[i].Z < 0
zInc[i] = -1
incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i])
if incrementVect[i].magnitude < 0.9
incrementVect[i] = nil
if not lines[2]
lines[2] = Vector3.new 0, 0, 0
if not lines[3]
lines[3] = Vector3.new 0, 0, 0
waterForceTag = stampData.CurrentParts\FindFirstChild("WaterForceTag", true)
waterForceDirectionTag = stampData.CurrentParts\FindFirstChild("WaterForceDirectionTag", true)
while stepVect[3].magnitude * 4 <= lines[3].magnitude
outerStepVectIndex = 1
while outerStepVectIndex < 4
stepVect[2] = Vector3.new 0, 0, 0
while stepVect[2].magnitude * 4 <= lines[2].magnitude
innerStepVectIndex = 1
while innerStepVectIndex < 4
stepVect[1] = Vector3.new 0, 0, 0
while stepVect[1].magnitude * 4 <= lines[1].magnitude
stepVectSum = stepVect[1] + stepVect[2] + stepVect[3]
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
-- check if overlaps player or part
okToStampTerrainBlock = checkTerrainBlockCollisions cellPos, checkHighScalabilityStamp
if okToStampTerrainBlock
if waterForceTag
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
)
cellSet = true
-- auto-wedge it?
if autoWedgeClusterParts
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
)
)
stepVect[1] = stepVect[1] + incrementVect[1]
if incrementVect[2]
while innerStepVectIndex < 4 and
worldAxes[innerStepVectIndex]\Dot(incrementVect[2]) == 0
innerStepVectIndex += 1
if innerStepVectIndex < 4
stepVect[2] = stepVect[2] +
worldAxes[innerStepVectIndex] *
worldAxes[innerStepVectIndex]\Dot incrementVect[2]
innerStepVectIndex += 1
else
stepVect[2] = Vector3.new(1, 0, 0)
innerStepVectIndex = 4 -- skip all remaining loops
if stepVect[2].magnitude * 4 > lines[2].magnitude
innerStepVectIndex = 4
if incrementVect[3]
while outerStepVectIndex < 4 and worldAxes[outerStepVectIndex]\Dot(incrementVect[3]) == 0
outerStepVectIndex += 1
if outerStepVectIndex < 4
stepVect[3] = stepVect[3] +
worldAxes[outerStepVectIndex] *
worldAxes[outerStepVectIndex]\Dot incrementVect[3]
outerStepVectIndex += 1
else -- skip all remaining loops
stepVect[3] = Vector3.new 1, 0, 0
outerStepVectIndex = 4
if stepVect[3].magnitude * 4 > lines[3].magnitude
outerStepVectIndex = 4
-- and also get rid of any HighScalabilityLine stuff if it's there
HighScalabilityLine.Start = nil
HighScalabilityLine.Adorn.Parent = nil
-- Mark for undo.
if cellSet
stampData.CurrentParts.Parent = nil
try
game\GetService"ChangeHistoryService"\SetWaypoint "StamperMulti"
cellSet
DoStamperMouseUp = (Mouse) ->
if not Mouse
error "Error: RbxStamper.DoStamperMouseUp: Mouse is nil"
return false
if not Mouse\IsA "Mouse"
error("Error: RbxStamper.DoStamperMouseUp: Mouse is of type", Mouse.className, "should be of type Mouse")
return false
if not stampData.Dragger
error "Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil"
return false
if not HighScalabilityLine
return false
local checkHighScalabilityStamp
if stampInModel
local canStamp
isHSLPart = isMegaClusterPart!
if isHSLPart and
HighScalabilityLine and
HighScalabilityLine.Start and
HighScalabilityLine.InternalLine and
HighScalabilityLine.InternalLine.magnitude > 0
-- we have an HSL line, test later
canStamp = true
checkHighScalabilityStamp = true
else
canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
if not canStamp
if stampFailedFunc
stampFailedFunc!
return false
-- if unstampable face, then don't let us stamp there!
if unstampableSurface
flashRedBox!
return false
-- recheck if we can stamp, as we just moved part
canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
if not canStamp
if stampFailedFunc
stampFailedFunc!
return false
-- 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
if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector)
flashRedBox!
return false
blockingParts = game.Workspace\FindPartsInRegion3(
Region3.new(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector),
stampData.CurrentParts,
100
)
for b = 1, #blockingParts
if isBlocker(blockingParts[b])
flashRedBox!
return false
alreadyPushedUp = {}
-- if no blocking model below, then see if stamping on top of a character
for b = 1, #blockingParts
if blockingParts[b].Parent and
not alreadyPushedUp[blockingParts[b].Parent] and
blockingParts[b].Parent\FindFirstChild"Humanoid" and
blockingParts[b].Parent\FindFirstChild"Humanoid"\IsA "Humanoid"
---------------------------------------------------------------------------
blockingPersonTorso = blockingParts[b].Parent\FindFirstChild "Torso"
alreadyPushedUp[blockingParts[b].Parent] = true
if blockingPersonTorso
-- 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)
newY = maxBB.Y + 3
if spaceAboveCharacter blockingPersonTorso, newY, stampData
blockingPersonTorso.CFrame = blockingPersonTorso.CFrame +
Vector3.new 0, newY - blockingPersonTorso.CFrame.p.Y, 0
else
-- if no space, we just error
flashRedBox!
return false
---------------------------------------------------------------------------
elseif (not configFound) and not (HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent) -- if no config then only stamp if it's a real HSL!
resetHighScalabilityLine!
return false
-- something will be stamped! so set the "StampedSomething" toggle to true
if game\FindFirstChild "Players"
if game.Players["LocalPlayer"]
if game.Players.LocalPlayer["Character"]
localChar = game.Players.LocalPlayer.Character
stampTracker = localChar\FindFirstChild "StampTracker"
if stampTracker and not stampTracker.Value
stampTracker.Value = true
-- if we drew a line of mega parts, stamp them out
if HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent and isMegaClusterPart!
if ResolveMegaClusterStamp(checkHighScalabilityStamp) or checkHighScalabilityStamp
-- kill the ghost part
stampData.CurrentParts.Parent = nil
return true
-- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff]
HighScalabilityLine.Start = nil
HighScalabilityLine.Adorn.Parent = nil
cluster = game.Workspace.Terrain
-- if target point is in cluster, just use cluster\SetCell
if isMegaClusterPart!
-- if targetCFrame is inside cluster, just set that cell to 1 and return
--cellPos = cluster\WorldToCell(targetCFrame.p)
local cellPos
if stampData.CurrentParts\IsA "Model"
cellPos = cluster\WorldToCell(stampData.CurrentParts\GetModelCFrame!.p)
else
cellPos = cluster\WorldToCell(stampData.CurrentParts.CFrame.p)
cMax = game.Workspace.Terrain.MaxExtents.Max
cMin = game.Workspace.Terrain.MaxExtents.Min
if checkTerrainBlockCollisions(cellPos, false)
clusterValues = stampData.CurrentParts\FindFirstChild("ClusterMaterial", true)
waterForceTag = stampData.CurrentParts\FindFirstChild("WaterForceTag", true)
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
if waterForceTag
cluster\SetWaterCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
Enum.WaterForce[waterForceTag.Value],
Enum.WaterDirection[waterForceDirectionTag.Value]
)
elseif not clusterValues
cluster\SetCell(
cellPos.X,
cellPos.Y,
cellPos.Z,
cellInfo.Material,
cellInfo.clusterType,
gInitial90DegreeRotations % 4
)
elseif clusterValues\IsA "Vector3Value"
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)
autoWedgeClusterParts = false
if stampData.CurrentParts\FindFirstChild "AutoWedge"
autoWedgeClusterParts = true
-- auto-wedge it
if autoWedgeClusterParts
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)
)
)
-- kill the ghost part
stampData.CurrentParts.Parent = nil
-- Mark for undo. It has to happen here or the selection display will come back also.
try
game\GetService"ChangeHistoryService"\SetWaypoint "StamperSingle"
return true
else
-- you tried to stamp a HSL-single part where one does not belong!
flashRedBox!
return false
getPlayer = ->
if game\FindFirstChild "Players"
if game.Players["LocalPlayer"]
return game.Players.LocalPlayer
return nil
-- 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"
if stampData.CurrentParts\IsA "Model"
-- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds... just don't ask X<
manualWeldTable = {}
manualWeldParentTable = {}
saveTheWelds(stampData.CurrentParts, manualWeldTable, manualWeldParentTable)
stampData.CurrentParts\BreakJoints!
stampData.CurrentParts\MakeJoints!
restoreTheWelds(manualWeldTable, manualWeldParentTable)
-- 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?
tempPlayerValue = getPlayer!
if tempPlayerValue?
playerIdTag.Value = tempPlayerValue.userId
if playerNameTag?
if game\FindFirstChild "Players" and game.Players["LocalPlayer"]
tempPlayerValue = game.Players.LocalPlayer
if tempPlayerValue?
playerNameTag.Value = tempPlayerValue.Name
-- ...and tag all inserted models for subsequent origin identification
-- if no RobloxModel tag already exists, then add it.
if not stampData.CurrentParts\FindFirstChild"RobloxModel"?
stringTag = Instance.new "BoolValue"
stringTag.Name = "RobloxModel"
stringTag.Parent = stampData.CurrentParts
if not stampData.CurrentParts\FindFirstChild"RobloxStamper"?
stringTag2 = Instance.new "BoolValue"
stringTag2.Name = "RobloxStamper"
stringTag2.Parent = stampData.CurrentParts
else
stampData.CurrentParts\BreakJoints!
if not stampData.CurrentParts\FindFirstChild"RobloxStamper"?
stringTag2 = Instance.new "BoolValue"
stringTag2.Name = "RobloxStamper"
stringTag2.Parent = stampData.CurrentParts
-- make sure all the joints are activated before restoring anchor states
if not createJoints
game.JointsService\CreateJoinAfterMoveJoints!
-- Restore the original properties for all parts being stamped
for part, transparency in pairs stampData.TransparencyTable
part.Transparency = transparency
for part, archivable in pairs stampData.ArchivableTable
part.Archivable = archivable
for part, material in pairs stampData.MaterialTable
part.Material = material
for part, collide in pairs stampData.CanCollideTable
part.CanCollide = collide
for part, anchored in pairs stampData.AnchoredTable
part.Anchored = anchored
for decal, transparency in pairs stampData.DecalTransparencyTable
decal.Transparency = transparency
for part, surfaces in pairs stampData.SurfaceTypeTable
loadSurfaceTypes(part, surfaces)
if isMegaClusterPart!
stampData.CurrentParts.Transparency = 0
-- 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 not stampData.CurrentParts\FindFirstChild"RobloxModel"?
stringTag = Instance.new "BoolValue"
stringTag.Name = "RobloxModel"
stringTag.Parent = stampData.CurrentParts
-- and make sure we don't delete it, now that it's not a ghost part
if ghostRemovalScript
ghostRemovalScript.Parent = nil
--Re-enable the scripts
for _, script in pairs stampData.DisabledScripts
script.Disabled = false
--Now that they are all marked enabled, reinsert them into the world so they start running
for _, script in pairs stampData.DisabledScripts
oldParent = script.Parent
script.Parent = nil
script\Clone!.Parent = oldParent
-- clear out more data
stampData.DisabledScripts = nil
stampData.Dragger = nil
stampData.CurrentParts = nil
try
game\GetService"ChangeHistoryService"\SetWaypoint "StampedObject"
true
pauseStamper = ->
for i = 1, #mouseCons do -- stop the mouse from doing anything
mouseCons[i]\disconnect!
mouseCons[i] = nil
mouseCons = {}
if stampData and stampData.CurrentParts -- remove our ghost part
stampData.CurrentParts.Parent = nil
stampData.CurrentParts\Remove!
resetHighScalabilityLine!
game.JointsService\ClearJoinAfterMoveJoints!
prepareUnjoinableSurfaces = (modelCFrame, parts, whichSurface) ->
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!
isPositive = 1
if whichSurface < 0
isPositive *= -1
whichSurface *= -1
surfaceNormal = isPositive * modelCFrame\vectorToWorldSpace(AXIS_VECTORS[whichSurface])
for i = 1, #parts
currPart = parts[i]
-- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable
surfaceNormalInLocalCoords = currPart.CFrame\vectorToObjectSpace surfaceNormal
if math.abs(surfaceNormalInLocalCoords.X) > math.abs surfaceNormalInLocalCoords.Y
if math.abs(surfaceNormalInLocalCoords.X) > math.abs surfaceNormalInLocalCoords.Z
if surfaceNormalInLocalCoords.X > 0
currPart.RightSurface = "Unjoinable"
else
currPart.LeftSurface = "Unjoinable"
else
if surfaceNormalInLocalCoords.Z > 0
currPart.BackSurface = "Unjoinable"
else
currPart.FrontSurface = "Unjoinable"
else
if math.abs(surfaceNormalInLocalCoords.Y) > math.abs surfaceNormalInLocalCoords.Z
if surfaceNormalInLocalCoords.Y > 0
currPart.TopSurface = "Unjoinable"
else
currPart.BottomSurface = "Unjoinable"
else
if surfaceNormalInLocalCoords.Z > 0
currPart.BackSurface = "Unjoinable"
else
currPart.FrontSurface = "Unjoinable"
resumeStamper = ->
clone, parts = prepareModel modelToStamp
if not clone or not parts
return
-- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable
unjoinableTag = clone\FindFirstChild "UnjoinableFaces", true
if unjoinableTag
for unjoinableSurface in string.gmatch unjoinableTag.Value, "[^,]*"
if tonumber unjoinableSurface
if clone\IsA "Model"
prepareUnjoinableSurfaces clone\GetModelCFrame!, parts, tonumber unjoinableSurface
else
prepareUnjoinableSurfaces clone.CFrame, parts, tonumber unjoinableSurface
stampData.ErrorBox = errorBox
clone.Parent = if stampInModel
stampInModel
else
game.Workspace
if clone\FindFirstChild "ClusterMaterial", true -- extract all info from vector
clusterMaterial = clone\FindFirstChild "ClusterMaterial", true
if clusterMaterial\IsA "Vector3Value"
cellInfo.Material = clusterMaterial.Value.X
cellInfo.clusterType = clusterMaterial.Value.Y
cellInfo.clusterOrientation = clusterMaterial.Value.Z
elseif clusterMaterial\IsA "IntValue"
cellInfo.Material = clusterMaterial.Value
try
mouseTarget = Mouse.Target
if mouseTarget and not mouseTarget.Parent\FindFirstChild"RobloxModel"?
game.JointsService\SetJoinAfterMoveTarget mouseTarget
else
game.JointsService\SetJoinAfterMoveTarget nil
game.JointsService\ShowPermissibleJoints!
for _, object in pairs stampData.DisabledScripts
if object.Name == "GhostRemovalScript"
object.Parent = stampData.CurrentParts
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 ->
return if movingLock or stampUpLock
movingLock = true
DoStamperMouseMove Mouse
movingLock = false
)
table.insert(
mouseCons,
Mouse.Button1Down\connect ->
DoStamperMouseDown Mouse
)
table.insert(
mouseCons,
Mouse.Button1Up\connect ->
stampUpLock = true
while movingLock
wait!
stamped.Value = DoStamperMouseUp Mouse
resetHighScalabilityLine!
stampUpLock = false
)
stamped.Value = false
resetStamperState = (newModelToStamp) ->
-- if we have a new model, swap it out
if newModelToStamp
if not newModelToStamp\IsA"Model" and not newModelToStamp\IsA "BasePart"
error "resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!"
modelToStamp = newModelToStamp
-- first clear our state
pauseStamper!
-- now lets load in the new model
resumeStamper!
-- 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 = (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"
error "Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!"
return nil
resetStamperState newStampModel
control.ReloadModel = ->
-- will automatically set stamper to get a new model of current model and start stamping with new model
resetStamperState!
control.Pause = -> -- temporarily stops stamping, use resume to start up again
if not control.Paused
pauseStamper!
control.Paused = true
else
print "RbxStamper Warning: Tried to call Control.Pause! when already paused"
control.Resume = -> -- resumes stamping, if currently paused
if control.Paused
resumeStamper!
control.Paused = false
else
print "RbxStamper Warning: Tried to call Control.Resume! without Pausing First"
control.ResetRotation = -> -- 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]
control.Destroy = -> -- Stops current Stamp operation and destroys control construct
for i = 1, #mouseCons
mouseCons[i]\disconnect!
mouseCons[i] = nil
keyCon?\disconnect!
game.JointsService\ClearJoinAfterMoveJoints!
adorn?\Destroy!
adornPart?\Destroy!
errorBox?\Destroy!
stampData?.Dragger?\Destroy!
stampData?.CurrentParts?\Destroy!
if control and control["Stamped"]
control.Stamped\Destroy!
control = nil
control
t.Help = (funcNameOrFunc) ->
--input argument can be a string or a function. Should return a description (of arguments and expected side effects)
switch funcNameOrFunc
when "GetStampModel", t.GetStampModel
"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"
when "SetupStamperDragger", t.SetupStamperDragger
"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."
t