I’ve been writing a custom placement module for anyone to easily setup a working placement system in minutes for their game. I have written two others before, but wanted to make a third one to add some new features. I have come across a problem with the new one where the model is offset for some reason. I also have a simple system that detects input to handle features such as rotation, canceling placement and moving up and down floors. The strange thing is, only one input is detected (the cancel key).
For the video I have the grid and interpolation off.
I have spent a while trying to debug these problems, but found nothing worked. I even tried coping code from the working module to see if I typed something wrong, but nothing changed. Maybe I just don’t see the problem, but I have no idea why this is happening.
I will put both the new module and the old one here. Note the new one has some comments with : P at the end of them. These are places where I think the problem is occurring.
New
-- SETTINGS
-- Bools
local interpolation = true -- Toggles interpolation (smoothing)
local moveByGrid = false -- Toggles grid system
local collisions = true -- Toggles collisions
local buildModePlacement = true -- Toggles "build mode" placement
local displayGridTexture = true -- Toggles the grid texture to be shown when placing
local smartDisplay = false -- Toggles smart display for the grid. If true, it will rescale the grid texture to match your gridsize
local enableFloors = true -- Toggles if the raise and lower keys will be enabled
local transparentModel = true -- Toggles if the model itself will be transparent
-- Color3
local collisionColor = Color3.fromRGB(255, 75, 75) -- Color of the hitbox when colliding
local hitboxColor = Color3.fromRGB(75, 255, 75) -- Color of the hitbox while not colliding
-- Integers
local maxHeight = 30 -- Max height you can place objects (in studs)
local floorStep = 10 -- The step (in studs) that the object will be raised or lowered
local rotationStep = 90 -- Rotation step
local lerpSpeed = 0 -- speed of interpolation. 0 = no interpolation, 0.9 = major interpolation
-- Numbers
local hitboxTransparency = 0.7 -- Hitbox transparency when placing
local transparencyDelta = 0.3 -- Transparency of the model itself (transparentModel must equal true)
-- Other
local gridTexture = "rbxassetid://2415319308"
-- DO NOT EDIT PAST THIS POINT UNLESS YOU KNOW WHAT YOUR DOING.
local placement = {}
placement.__index = placement
-- Essentials
local runService = game:GetService("RunService")
local userInputService = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local mouse = player:GetMouse()
-- math/cframe functions
local clamp = math.clamp
local floor = math.floor
local rad = math.rad
local abs = math.abs
local max = math.max
local cframe = CFrame.new
local angles = CFrame.Angles
-- Constructor variables
local grid
local itemLocation
local rotateKey
local terminateKey
local raiseKey
local lowerKey
-- Activation vatiables
local plot
local object
-- bools
--local currentlyPlacing
local canPlace
local isColliding
local stackable
local smartRot
local canActivate = true
local currentRot = false
-- values used for calculations
local speed --= 1 - clamp(abs(tonumber(lerpSpeed)), 0, 0.9)
local posX
local posY
local posZ
local rot
local lowerXBound
local upperXBound
local lowerZBound
local upperZBound
local initialY
-- collision variables
local collisionPoints
local collisionPoint
local collided
-- states
local states = {
"movement",
"placing",
"colliding",
"in-active"
}
local currentState = 4
-- other
local placedObjects
local primary
local humanoid = character:WaitForChild("Humanoid")
local function setCurrentState(state)
currentState = clamp(state, 1, 4)
end
-- Checks for collisions on the hitbox
local function checkHitbox()
if object then
collisionPoint = object.PrimaryPart.Touched:Connect(function() end)
collisionPoints = object.PrimaryPart:GetTouchingParts()
for i = 1, #collisionPoints do
if not collisionPoints[i]:IsDescendantOf(object) and not collisionPoints[i]:IsDescendantOf(character) then
setCurrentState(3)
break
end
end
collisionPoint:Disconnect()
return collided
end
end
-- Changes the color of the hitbox depending on the current state
local function editHitboxColor()
if currentState == 3 then
object.PrimaryPart.Color = collisionColor
else
object.PrimaryPart.Color = hitboxColor
end
end
-- switches the floor depending on the value given
local function editFloor(f)
if enableFloors then
if posY < maxHeight and posY > initialY then
if f == 1 then
posY = posY + floor(abs(floorStep))
else
posY = posY - floor(abs(floorStep))
end
end
end
end
-- handles the grid texture
local function displayGrid()
if displayGridTexture then
local gridTex = Instance.new("Texture")
gridTex.Name = "GridTexture"
gridTex.Texture = gridTexture
gridTex.Parent = plot
gridTex.Face = Enum.NormalId.Top
gridTex.StudsPerTileU = 2
gridTex.StudsPerTileV = 2
if smartDisplay then
gridTex.StudsPerTileU = grid
gridTex.StudsPerTileV = grid
end
end
end
local function rotate()
if smartRot then
if currentRot then
rot = rot + rotationStep
else
rot = rot - rotationStep
end
else
rot = rot + rotationStep
end
currentRot = not currentRot
end
local function calculateYPos(tp, ts, o)
return (tp + ts / 2) + o / 2
end
-- This function may be allowing the object to leave the plot. Not sure if this function is the problem though. : P (maybe)
local function bounds()
if currentRot then
lowerXBound = plot.Position.X - (plot.Size.X / 2)
upperXBound = plot.Position.X + (plot.Size.X / 2) - primary.Size.X
lowerZBound = plot.Position.Z - (plot.Size.Z / 2)
upperZBound = plot.Position.Z + (plot.Size.Z / 2) + primary.Size.Z
else
lowerXBound = plot.Position.X - (plot.Size.X / 2)
upperXBound = plot.Position.X + (plot.Size.X / 2) - primary.Size.Z
lowerZBound = plot.Position.Z - (plot.Size.Z / 2)
upperZBound = plot.Position.Z + (plot.Size.Z / 2) + primary.Size.X
end
posX = math.clamp(posX, lowerXBound, upperXBound)
posZ = math.clamp(posZ, lowerZBound, upperZBound)
end
-- Calculates the position of the object : P (maybe)
local function calculateItemLocation()
if moveByGrid then
posX = math.floor((mouse.Hit.X / grid) + 0.5) * grid
posZ = math.floor((mouse.Hit.Z / grid) + 0.5) * grid
else
posX = mouse.Hit.X
posZ = mouse.Hit.Z
end
bounds()
if not enableFloors and stackable then
posY = calculateYPos(mouse.Target.Position.Y, mouse.Target.Size.Y, primary.Size.Y)
end
--posY = clamp(posY, initialY, maxHeight)
end
-- Sets the position of the object
local function translateObj()
if currentState ~= 4 then
-- I dont know if it's the calc funcion of the movement itself. To me, everything is the same as my V2 module. Maybe it's just me though. : P
calculateItemLocation()
print(rot)
if currentRot then
object:SetPrimaryPartCFrame(primary.CFrame:Lerp(cframe(posX, posY, posZ) * cframe(primary.Size.X / 2, 0, primary.Size.Z / 2) * angles(0, math.rad(rot), 0), speed))
else
object:SetPrimaryPartCFrame(primary.CFrame:Lerp(cframe(posX, posY, posZ) * cframe(primary.Size.Z / 2, 0, primary.Size.X / 2) * angles(0, math.rad(rot), 0), speed))
end
end
end
-- handles user input
local function getInput(input, gpe)
if currentState == 1 then
print(input.KeyCode == rotateKey) -- prints true : P
if input.KeyCode == raiseKey then
editFloor(1)
elseif input.KeyCode == lowerKey then
editFloor(2)
elseif input.KeyCode == terminateKey then
placement:terminate()
elseif input.KeyCode == rotateKey then
print("input") -- never printed : P
rotate() -- never called : P
else
print("input detected") -- this was used for debugging
end
end
end
-- Verifys that the plane which the object is going to be placed upon is the correct size
local function verifyPlane()
if max(plot.Size.X, plot.Size.Z) > grid and grid ~= 0 then
if plot.Size.X / grid == floor(plot.Size.X / grid) and plot.Size.Z / grid == floor(plot.Size.Z / grid) then
return true
else
return false
end
else
return nil
end
end
-- Checks if there are any problems with the users setup
local function approveActivation()
if verifyPlane() ~= nil and not verifyPlane() then
warn("The object that the model is moving on is not scaled correctly. Consider changing it.")
end
if grid > max(plot.Size.X, plot.Size.Z) then
error("Grid size is larger than the plot size. To fix this, try lowering the grid size.")
end
end
-- Sets up placement
function placement.new(g, objs, r, t, u, l)
local data = {}
local metaData = setmetatable(data, placement)
grid = abs(tonumber(g))
itemLocation = objs
rotateKey = r
terminateKey = t
raiseKey = r
lowerKey = l
data.gridsize = grid
data.items = objs
data.raise = rotateKey
data.cancel = terminateKey
data.raise = raiseKey
data.lower = lowerKey
return data
end
function placement:getCurrentState()
return states[currentState]
end
-- Requests to place down the object
function placement:requestPlacement(re, loc)
end
-- Terminates placement
function placement:terminate()
stackable = nil
canPlace = nil
smartRot = nil
object:Destroy()
object = nil
if displayGridTexture then
for i, v in next, plot:GetChildren() do
if v then
if v.Name == "GridTexture" and v:IsA("Texture") then
v:Destroy()
end
end
end
end
-- currentlyPlacing = false
setCurrentState(4)
canActivate = true
return
end
-- Activates placement
function placement:activate(id, pobj, plt, stk, r)
if object then
object:Destroy()
object = nil
end
plot = plt
object = itemLocation:FindFirstChild(tostring(id))
placedObjects = pobj
approveActivation()
object = itemLocation:FindFirstChild(id):Clone()
object.PrimaryPart.Transparency = hitboxTransparency
object.PrimaryPart.Color = hitboxColor
for i, o in pairs(object:GetDescendants()) do
if o then
if o:IsA("Part") or o:IsA("UnionOperation") or o:IsA("MeshPart") then
o.CanCollide = false
if transparentModel then
o.Transparency = o.Transparency + transparencyDelta
end
end
end
end
-- currentlyPlacing = true
stackable = stk
smartRot = r
if not stk then
mouse.TargetFilter = pobj
else
mouse.TargetFilter = object
end
initialY = calculateYPos(plt.Position.Y, plt.Size.Y, object.PrimaryPart.Size.Y)
posY = initialY
speed = 0
rot = 0
translateObj()
displayGrid()
if interpolation then
speed = clamp(abs(tonumber(1 - lerpSpeed)), 0, 0.9)
else
speed = 0
end
primary = object.PrimaryPart
object.Parent = pobj
setCurrentState(1)
end
runService:BindToRenderStep("Input", Enum.RenderPriority.Input.Value, translateObj)
userInputService.InputBegan:Connect(getInput) -- : P
return placement
Old
-- Settings
-- Grid
local MoveByGrid = false -- If you wan't the model to move by grid specified units.
local GridSize = 2 -- In studs.
local DisplayGrid = true -- If you wan't to display a grid.
local SmartDisplay = true -- If you want the module to try and autoscale the texture to match your grid (may alter resolution of texture).
local GridTexture = "rbxassetid://2415319308" -- Texture used when grid is displayed.
-- Linear Interpolation (smoothing)
local Interpolate = false -- Enable smoothing (Interpolation)
local InterpolateSpeed = 0.9 -- Speed of movement (0 = instant, 1 = no movement. Defualt = 0.7)
-- Basic Settings - Start
local RotationStep = 90 -- In degrees.
local FloorStep = 10 -- In studs.
local MaxHeight = 10 -- In studs.
local CollisionCheckCooldown = 0.3 -- In seconds
-- Bools (true or false values)
local DetectCollisions = true -- If you wan't to have a collision system.
local IgnoreItems = true -- If you want the mouse to ignore the items currently placed down.
local BuildModeEnabled = true -- If you want continual placement.
local EnableFloors = true -- If you want a floor system.
local TransparentModels = true -- if you want the models to be transparent while placing
-- Basic Settings - End
-- Advanced Settings - Start
local CollisionColor = Color3.fromRGB(255, 55, 55) -- The RGB value for when collision is detected.
local PlacingColor = Color3.fromRGB(55, 255, 55) -- The RGB value for when collision is not detected.
-- Keybinds
local KeyCodeCancel = Enum.KeyCode.X -- Key used to cancel placement.
local KeyCodeRotate = Enum.KeyCode.R -- Key used to rotate the model being placed.
local KeyCodeRaiseFloor = Enum.KeyCode.U -- Key used to raise the floor.
local KeyCodeLowerFloor = Enum.KeyCode.L -- Key used to lower the floor.
-- Advanced Settings - End
local transparentDelta = 0.6
local hitboxTransparency = 0.8
local step = 1.1
-- DO NOT EDIT PAST THIS POINT UNLESS YOU KNOW WHAT YOUR DOING!
local PlacementModuleV2 = {}
local runService = game:GetService("RunService")
local userInputService = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local mouse = player:GetMouse()
local posX
local posY
local posZ
local startingY
local currentRot = true
local smartRot = false
local lastY
local rot = 0
local c = 0
local modelInfo = {}
local cframe
local grid = math.abs(tonumber(GridSize))
local model
local plot
local objs
local collisionPoints
local collisionPoint
local placingMode
local canPlace
local placing
local colliding
local stacking
local canStart = true
wait(0.1) -- DO NOT REMOVE (It will error if you remove)
local humanoid = character:FindFirstChild("Humanoid")
local function EditColor()
if PlacementModuleV2:GetCurrentState() == "Collision" and model then
model.PrimaryPart.Color = CollisionColor
elseif PlacementModuleV2:GetCurrentState() ~= "Collision" and model then
model.PrimaryPart.Color = PlacingColor
end
end
local function CheckHitbox()
if model then
colliding = false
collisionPoint = model.PrimaryPart.Touched:Connect(function() end)
collisionPoints = model.PrimaryPart:GetTouchingParts()
for i = 1, #collisionPoints do
if not collisionPoints[i]:IsDescendantOf(model) and not collisionPoints[i]:IsDescendantOf(character) then
colliding = true
break
end
end
collisionPoint:Disconnect()
return colliding
end
end
local function StayInBounds()
local lowerBoundX
local upperBoundX
local lowerBoundZ
local upperBoundZ
if currentRot then
lowerBoundX = plot.Position.X - (plot.Size.X / 2)
upperBoundX = plot.Position.X + (plot.Size.X / 2) - model.PrimaryPart.Size.X
lowerBoundZ = plot.Position.Z - (plot.Size.Z / 2)
upperBoundZ = plot.Position.Z + (plot.Size.Z / 2) - model.PrimaryPart.Size.Z
else
lowerBoundX = plot.Position.X - (plot.Size.X / 2)
upperBoundX = plot.Position.X + (plot.Size.X / 2) - model.PrimaryPart.Size.Z
lowerBoundZ = plot.Position.Z - (plot.Size.Z / 2)
upperBoundZ = plot.Position.Z + (plot.Size.Z / 2) - model.PrimaryPart.Size.X
end
posX = math.clamp(posX, lowerBoundX, upperBoundX)
posZ = math.clamp(posZ, lowerBoundZ, upperBoundZ)
end
local function CalcualateNewPosition()
if MoveByGrid then
posX = math.floor((mouse.Hit.X / grid) + 0.5) * grid
posZ = math.floor((mouse.Hit.Z / grid) + 0.5) * grid
else
posX = mouse.Hit.X
posZ = mouse.Hit.Z
end
if EnableFloors and not stacking then
if posY > MaxHeight then
posY = MaxHeight
elseif posY < startingY then
posY = startingY
end
end
StayInBounds()
if stacking then
--posY = math.floor(mouse.Hit.Y) + step
posY = (mouse.Target.Position.Y + mouse.Target.Size.Y / 2) + model.PrimaryPart.Size.Y / 2
if posY > MaxHeight then
posY = MaxHeight
elseif posY < startingY then
posY = startingY
end
end
end
local function CalcFinalCFrame()
if currentRot then
cframe = CFrame.new(posX, posY, posZ) * CFrame.new(model.PrimaryPart.Size.X / 2, 0, model.PrimaryPart.Size.Z / 2)
else
cframe = CFrame.new(posX, posY, posZ) * CFrame.new(model.PrimaryPart.Size.Z / 2, 0, model.PrimaryPart.Size.X / 2)
end
end
local function Calc(tf)
if currentRot then
if Interpolate then
model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:Lerp(CFrame.new(posX, posY, posZ) * CFrame.new(model.PrimaryPart.Size.X / 2, 0, model.PrimaryPart.Size.Z / 2) * CFrame.Angles(0, math.rad(rot), 0), 1 - InterpolateSpeed))
else
model:SetPrimaryPartCFrame(CFrame.new(posX, posY, posZ) * CFrame.new(model.PrimaryPart.Size.X / 2, 0, model.PrimaryPart.Size.Z / 2) * CFrame.Angles(0, math.rad(rot), 0))
end
else
if Interpolate then
model:SetPrimaryPartCFrame(model.PrimaryPart.CFrame:Lerp(CFrame.new(posX, posY, posZ) * CFrame.new(model.PrimaryPart.Size.Z / 2, 0, model.PrimaryPart.Size.X / 2) * CFrame.Angles(0, math.rad(rot), 0), 1 - InterpolateSpeed))
else
model:SetPrimaryPartCFrame((CFrame.new(posX, posY, posZ) * CFrame.new(model.PrimaryPart.Size.Z / 2, 0, model.PrimaryPart.Size.X / 2)) * CFrame.Angles(0, math.rad(rot), 0))
end
end
end
local function ModifyCoordinates()
if placingMode and canPlace and model then
CalcualateNewPosition()
Calc(true)
if DetectCollisions then
CheckHitbox()
EditColor()
end
end
end
local function EditFloor(g)
if posY <= MaxHeight and posY >= startingY then
if g == 2 then
posY = posY + FloorStep
else
posY = posY - FloorStep
end
end
end
local function CheckRotation()
if model then
if currentRot then
currentRot = false
else
currentRot = true
end
end
end
local function RotateModel()
if smartRot then
if currentRot then
rot = rot + RotationStep
else
rot = rot - RotationStep
end
else
rot = rot + RotationStep
end
CheckRotation()
end
local function RemovePlacement(tf)
placingMode = false
canPlace = false
canStart = true
if DisplayGrid then
for i, v in pairs(plot:GetChildren()) do
if v then
if v:IsA("Texture") and v.Name == "GridTexture" then
v:Destroy()
end
end
end
end
stacking = false
if not BuildModeEnabled and tf then
model:Destroy()
model = nil
else
model:Destroy()
model = nil
end
end
local function InputDetector(input, gpe)
if placingMode and model then
if input.KeyCode == KeyCodeCancel then
RemovePlacement()
elseif input.KeyCode == KeyCodeRaiseFloor then
EditFloor(2)
elseif input.KeyCode == KeyCodeLowerFloor then
EditFloor(1)
-- elseif input.KeyCode == KeyCodeCancel then
-- RemovePlacement(false)
elseif input.KeyCode == KeyCodeRotate then
RotateModel()
end
end
end
local function DisplayGridOnCanvas()
if DisplayGrid then
local gridTex = Instance.new("Texture")
gridTex.Name = "GridTexture"
gridTex.Texture = GridTexture
gridTex.Parent = plot
gridTex.Face = Enum.NormalId.Top
gridTex.StudsPerTileU = 2
gridTex.StudsPerTileV = 2
if SmartDisplay then
gridTex.StudsPerTileU = grid
gridTex.StudsPerTileV = grid
end
end
end
local function GetModel(loc, id, ignoreLocation)
model = loc:FindFirstChild(id):Clone()
startingY, posY = model.PrimaryPart.CFrame.Y, model.PrimaryPart.CFrame.Y
model:SetPrimaryPartCFrame(CFrame.new(posX, lastY, posZ))
model.Parent = ignoreLocation
for i, m in pairs(model:GetDescendants()) do
if m then
if m:IsA("Part") or m:IsA("UnionOperation") or m:IsA("MeshPart") then
m.CanCollide = false
if TransparentModels then
m.Transparency = m.Transparency + transparentDelta
end
end
end
end
model.PrimaryPart.Transparency = hitboxTransparency
if IgnoreItems and not stacking then
mouse.TargetFilter = ignoreLocation
else
mouse.TargetFilter = model
end
end
local function Setup(stk, location, id, loc, sr)
placingMode = true
canPlace = true
canStart = false
stacking = stk
smartRot = sr
if BuildModeEnabled then
canStart = true
end
GetModel(loc, id, location)
DisplayGridOnCanvas()
end
function PlacementModuleV2:Finish(e)
if placingMode and model and not BuildModeEnabled then
wait(CollisionCheckCooldown)
Calc(currentRot)
CalcualateNewPosition()
CalcFinalCFrame()
CheckHitbox()
if not colliding then
e:FireServer(self:GetModelInfo(), model.Parent, cframe)
RemovePlacement(false)
end
elseif BuildModeEnabled and canPlace and placingMode and not colliding and model then
lastY = model.PrimaryPart.CFrame.Y
wait(CollisionCheckCooldown)
Calc(currentRot)
CalcualateNewPosition()
CalcFinalCFrame()
CheckHitbox()
if not colliding then
e:FireServer(self:GetModelInfo(), model.Parent, cframe)
end
end
end
function PlacementModuleV2:CancelPlacementManual()
RemovePlacement(false)
end
function PlacementModuleV2:GetModelInfo()
if model then
modelInfo = {
["Name"] = model.Name;
["CollisionState"] = colliding;
["CFrame"] = {
["X"] = model.PrimaryPart.CFrame.X,
["Y"] = model.PrimaryPart.CFrame.Y,
["Z"] = model.PrimaryPart.CFrame.Z,
["Rotation"] = model.PrimaryPart.Orientation.Y
}
}
return modelInfo
end
end
function PlacementModuleV2:GetCurrentState()
local state
if placingMode and not colliding then
state = "Movement"
elseif placing and not colliding then
state = "Placing"
elseif colliding then
state = "Collision"
elseif canStart and not placingMode then
state = "Waiting"
else
state = nil
end
return state
end
function PlacementModuleV2:ResumeState(msg)
if self:GetCurrentState() == "Movement" then
placingMode = true
canPlace = true
return msg
elseif self:GetCurrentState() == "Placing" then
return "Not developed yet"
elseif self:GetCurrentState() == "Collision" then
colliding = nil
placingMode = true
canPlace = true
return msg
elseif self:GetCurrentState() == "Waiting" then
return "Not developed yet"
end
end
function PlacementModuleV2:PauseState(msg)
if self:GetCurrentState() == "Movement" then
placingMode = nil
canPlace = nil
return msg
elseif self:GetCurrentState() == "Placing" then
return "Not developed yet"
elseif self:GetCurrentState() == "Collision" then
placingMode = nil
canPlace = nil
colliding = true
return msg
elseif self:GetCurrentState() == "Waiting" then
return "Not developed yet"
end
end
function PlacementModuleV2:new(id, location, plt, loc, stack, smtr)
if canStart and not placingMode and not BuildModeEnabled then
plot = plt
Setup(stack, loc, id, location, smtr)
elseif BuildModeEnabled then
plot = plt
if model then
model:Destroy()
end
Setup(stack, loc, id, location, smtr)
else
return self:GetCurrentState()
end
end
humanoid.Died:Connect(function()
RemovePlacement(false)
end)
runService:BindToRenderStep("Input", Enum.RenderPriority.Input.Value, ModifyCoordinates)
userInputService.InputBegan:Connect(InputDetector)
return PlacementModuleV2
Here are how the modules are being used from local scripts:
New
local itemPlacement = require(game.ReplicatedStorage.Modules.PlacementModuleV3)
local remote = game.ReplicatedStorage.Events.PlaceObj
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local button = script.Parent.version3
itemPlacement.new(
2,
game.ReplicatedStorage.Models,
Enum.KeyCode.R, Enum.KeyCode.X, Enum.KeyCode.U, Enum.KeyCode.L
)
button.MouseButton1Click:Connect(function()
itemPlacement:activate("Wall", workspace.Plots.Plot1.PlacedObjects, workspace.Plots.Plot1.Plot, false, true)
end)
-- ignore this
mouse.Button1Down:Connect(function()
end)
Old
local placementModule = require(game.ReplicatedStorage.Modules.PlacementModuleV2EDIT)
local remote = game.ReplicatedStorage.Events.PlaceObj
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local button = script.Parent.version1
button.MouseButton1Click:Connect(function()
placementModule:new("Wall", game.ReplicatedStorage.Models, workspace.Plots.Plot1.Plot, workspace.Plots.Plot1.PlacedObjects, true, true)
end)
-- ignore this
mouse.Button1Down:Connect(function()
placementModule:Finish(remote)
end)
One more thing, the old module is not the same as the released version of it. I just added the bounds function in. Other than that, it’s the same. Just something to be aware of.
Thanks!