2394 lines
73 KiB
Plaintext
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
|