Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Settings
- --Bools
- local interpolation = true
- local moveByGrid = true
- local buildmodePlacement = true
- --ints
- local rotationStep = 90
- local maxHeight = 90
- --Numbers/Floats
- local lerpLevel = 0.1
- --Other
- local gridTexture = "rbxassetid://2415319308"
- local placement = {}
- placement.__index = placement
- local players = game:GetService("Players")
- local runservice = game:GetService("RunService")
- local contextActionService = game:GetService("ContextActionService")
- local player = players.LocalPlayer
- local character = player.Character or player.CharacterAdded:Wait()
- local mouse = player:GetMouse()
- -- Constructor Variables
- local GRID_SIZE
- local ITEM_LOCATION
- local ROTATE_KEY
- local TERMINATE_KEY
- -- activation variables
- local object
- local placedObjects
- local plot
- local stackable
- local primary
- -- Variables that used in calculation
- local posX
- local posY
- local posZ
- local speed = 1
- local rotation = 0
- local rotationVal = false
- -- other
- local collided = nil
- local selectionbox
- local TerminateButton = game.Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("UI"):WaitForChild("TRASH")
- -- quick type vars
- local cframe = CFrame.new
- local angles = CFrame.fromEulerAnglesXYZ
- local clamp = math.clamp
- local function renderGrid()
- if plot:FindFirstChildOfClass("Texture") then
- plot:FindFirstChildOfClass("Texture"):Destroy()
- end
- local texture = Instance.new("Texture")
- texture.StudsPerTileU = GRID_SIZE
- texture.StudsPerTileV = GRID_SIZE
- texture.Texture = gridTexture
- texture.Face = Enum.NormalId.Top
- texture.Parent = plot
- end
- local function changeHitboxColor()
- if primary then
- if collided then
- selectionbox.Adornee = primary
- selectionbox.Color3 = Color3.new(1,0,0)
- else
- selectionbox.Adornee = primary
- selectionbox.Color3 = Color3.new(0,1,0)
- end
- end
- end
- local function handleCollisions()
- if object then
- collided = false
- local collisionPoint = primary.Touched:Connect(function() end)
- local collisionPoints = primary:GetTouchingParts()
- for i = 1, #collisionPoints, 1 do
- if not collisionPoints[i]:IsDescendantOf(object) and not collisionPoints[i]:IsDescendantOf(character) then
- collided = true
- break
- end
- end
- collisionPoint:Disconnect()
- return
- end
- end
- local function bounds(cf, offsetX, offsetZ)
- local LOWER_X_BOUND
- local LOWER_Z_BOUND
- local UPPER_X_BOUND
- local UPPER_Z_BOUND
- LOWER_X_BOUND = plot.Position.X - (plot.Size.X*0.5) + offsetX
- UPPER_X_BOUND = plot.Position.X + (plot.Size.X*0.5) - offsetX
- LOWER_Z_BOUND = plot.Position.Z - (plot.Size.Z*0.5) + offsetZ
- UPPER_Z_BOUND = plot.Position.Z + (plot.Size.Z*0.5) - offsetZ
- local newX = clamp(cf.X, LOWER_X_BOUND, UPPER_X_BOUND)
- local newZ = clamp(cf.Z, LOWER_Z_BOUND, UPPER_Z_BOUND)
- return cframe(newX,posY,newZ)
- end
- -- calculates the initial y position above an object
- local function calculateYPosition(toP,toS,oS)
- return (toP + toS*0.5) + oS*0.5
- end
- local function rotate(actionName, inputState, inputObj)
- if inputState == Enum.UserInputState.Begin then
- rotation += rotationStep
- rotationVal = not rotationVal
- end
- end
- local function cancel(actionName, inputState, inputObj)
- if inputState == Enum.UserInputState.Begin then
- object:Destroy()
- selectionbox:Destroy()
- --plot:FindFirstChild("Texture"):Destroy()
- mouse.TargetFilter = nil
- object = nil
- end
- end
- TerminateButton.MouseButton1Click:Connect(function()
- if object and selectionbox then
- cancel("TerminateButtonClick", Enum.UserInputState.End, nil)
- end
- end)
- local function snap(c)
- local newX = math.round(c.X/GRID_SIZE)*GRID_SIZE
- local newZ = math.round(c.Z/GRID_SIZE)*GRID_SIZE
- return cframe(newX, 0 , newZ)
- end
- -- sets models position based on pivot
- local function calculateItemPosition()
- local finalCFrame = cframe(0,0,0)
- local x, z
- local offsetX,offsetZ
- if rotationVal then
- offsetX, offsetZ = primary.Size.X*0.2, primary.Size.Z*0.5
- x, z = mouse.Hit.X - offsetX, mouse.Hit.Z - offsetZ
- else
- offsetX, offsetZ = primary.Size.Z*0.2, primary.Size.X*0.5
- x, z = mouse.Hit.X - offsetX, mouse.Hit.Z - offsetZ
- end
- if stackable and mouse.Target and mouse.Target:IsDescendantOf(plot) then
- posY = calculateYPosition(mouse.Target.Position.Y, mouse.Target.Size.Y,primary.Size.Y)
- else
- posY = calculateYPosition(plot.Position.Y,plot.Size.Y,primary.Size.Y)
- end
- if moveByGrid then
- local pltCFrame = cframe(plot.CFrame.X,plot.CFrame.Y,plot.CFrame.Z)
- local pos = cframe(x,0,z)
- pos = snap(pos* pltCFrame:Inverse())
- finalCFrame = pos*pltCFrame*cframe(offsetX, 0 , offsetZ)
- else
- finalCFrame = cframe(mouse.Hit.X, posY, mouse.Hit.Z)
- end
- finalCFrame = bounds(cframe(finalCFrame.X, posY, finalCFrame.Z), offsetX,offsetZ)
- return finalCFrame*angles(0,math.rad(rotation), 0)
- end
- local function unbindInputs()
- contextActionService:UnbindAction("Rotate")
- contextActionService:UnbindAction("Cancel")
- end
- local function bindInputs()
- contextActionService:BindAction("Rotate", rotate, false, ROTATE_KEY)
- contextActionService:BindAction("Cancel", cancel, false, TERMINATE_KEY)
- end
- local function translateObj()
- if placedObjects and object and object.Parent == placedObjects then
- handleCollisions()
- changeHitboxColor()
- object:PivotTo(object.PrimaryPart.CFrame:Lerp(calculateItemPosition(), speed))
- end
- end
- -- returns the instant/non interpolated position of the object at any given amount
- local function getInstantCFrame()
- return calculateItemPosition()
- end
- function placement:place(remote)
- if not collided and object then
- local cf = getInstantCFrame()
- remote:InvokeServer(object.Name, placedObjects, cf, plot)
- end
- end
- -- check of the model will snap evenly on the plot
- local function verifyPlane()
- return plot.Size.X%GRID_SIZE == 0 and plot.Size.Z%GRID_SIZE == 0
- end
- -- confirm that the settings are valid for placement
- local function approvedPlacement()
- if not verifyPlane() then
- warn("the model cannot snap on the plot. please change the plot size to fix this.")
- return false
- end
- if GRID_SIZE >= math.min(plot.Size.X,plot.Size.Z) then
- error("Grid Size is too close to the size of the plot on at least one azis.")
- end
- return true
- end
- -- Constructor Function
- function placement.new(g, objs, r, t)
- local data = {}
- local metaData = setmetatable(data, placement)
- GRID_SIZE = g
- ITEM_LOCATION = objs
- ROTATE_KEY = r
- TERMINATE_KEY = t
- data.grid = GRID_SIZE
- data.itemlocation = ITEM_LOCATION
- data.rotatekey = ROTATE_KEY or Enum.KeyCode.R
- data.terminatekey = TERMINATE_KEY or Enum.KeyCode.X
- return data
- end
- -- acitvates placement (id = name, pobjs = placedobjects,plt = plot.stk = stackable)
- function placement:activate(id, pobjs, plt, stk)
- if object then
- object:Destroy()
- object = nil -- Ensure object reference is cleared
- end
- object = ITEM_LOCATION:FindFirstChild(id):Clone()
- selectionbox = Instance.new("SelectionBox")
- selectionbox.LineThickness = 0.05
- selectionbox.Parent = workspace
- primary = object.PrimaryPart
- placedObjects = pobjs
- plot = plt
- stackable = stk
- rotation = 0
- rotationVal = true
- -- sets can collide to false so that collisions are ignored
- for i, v in pairs(object:GetDescendants()) do
- if v:IsA("BasePart") then
- v.CanCollide = false
- v.Transparency = 0.5
- end
- end
- -- Check if placement can activate properly
- if not approvedPlacement() then
- return "Placement could not activate"
- end
- if not stackable then
- mouse.TargetFilter = placedObjects
- else
- mouse.TargetFilter = object
- end
- -- Speed setup
- local tempspeed = 1
- if interpolation then
- tempspeed = math.clamp(math.abs(tonumber(1 - lerpLevel)), 0, 10)
- speed = 1
- end
- renderGrid()
- object.Parent = placedObjects
- task.wait()
- bindInputs()
- speed = tempspeed
- end
- runservice:BindToRenderStep("Input", Enum.RenderPriority.Input.Value,translateObj)
- return placement
Advertisement
Add Comment
Please, Sign In to add comment