Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Placement handler for my candy cooking game sweet empire
- -- Youtube Link [[https://youtu.be/WHLzknKAk6w]]
- -- Bools
- local interpolation = true
- local moveByGrid = true
- local buildModePlacement = true
- -- Ints
- local rotationStep = 90
- local maxHeight = 90
- -- Numbers/Floats
- local lerpLevel = 0.8 -- 0 = instand snapping, 1 = no movement at all/high interpolation
- -- Other
- local gridTexture = "rbxassetid://7956721784" -- texture player sees when placing a object
- local placement = {}
- placement.__index = placement
- local players = game:GetService("Players")
- local runService = game:GetService("RunService")
- local contextActionService = game:GetService("ContextActionService")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- 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 plotModel
- local stackable
- local primary
- -- Variables used in calculations
- local posX
- local posY
- local posZ
- local speed = 1
- local rotation = 0
- local rotationVal = false
- -- Other
- local DataEvent = ReplicatedStorage.Events.RemoteFunctions.GetData
- local collided = nil
- -- Quick type variables
- local cframe = CFrame.new
- local angles = CFrame.fromEulerAnglesXYZ
- local clamp = math.clamp
- local function renderGrid()
- -- So that multiple textures dont get added
- if plot:FindFirstChildOfClass("Texture") then
- return
- end
- local texture = Instance.new("Texture")
- texture.StudsPerTileU = GRID_SIZE*2
- texture.StudsPerTileV = GRID_SIZE*2
- texture.Texture = gridTexture
- texture.Face = Enum.NormalId.Top
- texture.Parent = plot
- end
- -- Handles collisions on the hitbox
- local function changeHitBoxColor()
- if primary then
- if collided then
- primary.Transparency = 0.7
- primary.Color = Color3.fromRGB(255, 0, 4)
- else
- primary.Transparency = 0.7
- primary.Color = Color3.fromRGB(4, 255, 0)
- end
- end
- end
- -- Handles collisions on the hitbox
- 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(plotModel) 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
- -- Calculates the bounds at each corner including model scale
- 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
- function placement.rotate(actionName, inputState, inputObj, scriptcall)
- if inputState == Enum.UserInputState.Begin or scriptcall then
- rotation += rotationStep
- rotationVal = not rotationVal
- end
- end
- local function unbindInputs()
- contextActionService:UnbindAction("Rotate")
- contextActionService:UnbindAction("Cancel")
- end
- function placement.cancel(actionName, inputState, inputObj, scriptcall)
- if inputState == Enum.UserInputState.Begin or scriptcall then
- -- Stop the rendering and movement logic immediately
- runService:UnbindFromRenderStep("Input")
- -- Destroy the object
- if object then
- object:Destroy()
- object = nil -- Ensure the reference is cleared
- end
- -- Destroy the texture from the plot
- if plot and plot:FindFirstChild("Texture") then
- plot:FindFirstChild("Texture"):Destroy()
- end
- -- Clear the mouse target filter
- mouse.TargetFilter = nil
- -- Unbind inputs to prevent further interactions
- unbindInputs()
- -- Reset placement-related variables to ensure proper reinitialization on retry
- placedObjects = nil
- plot = nil
- stackable = nil
- primary = nil
- rotation = 0
- rotationVal = false
- collided = nil -- Reset collision state
- speed = 1 -- Reset speed for consistency
- 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
- -- Calculates the models position based on grid
- local function calculateItemPosition()
- local finalCFrame = cframe(0,0,0)
- local x, z
- local offsetX, offsetZ
- -- Calculates offsets and swaps them on rotation to keep object snapped within the grid units
- if rotationVal then
- offsetX, offsetZ = primary.Size.X*0.5, primary.Size.Z*0.5
- x, z = mouse.Hit.X - offsetX, mouse.Hit.Z - offsetZ
- else
- offsetX, offsetZ = primary.Size.Z*0.5, primary.Size.X*0.5
- x, z = mouse.Hit.X - offsetX, mouse.Hit.Z - offsetZ
- end
- -- Calculates the y position
- 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
- -- Calculates either snapped position or non snapped position
- 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 bindInputs()
- contextActionService:BindAction("Rotate", placement.rotate, false, ROTATE_KEY) -- only for pc right now
- contextActionService:BindAction("Cancel", placement.cancel, false, TERMINATE_KEY)
- end
- -- Sets models position based on pivot
- local function translateObj()
- if placedObjects and object.Parent == placedObjects then
- handleCollisions()
- changeHitBoxColor()
- object:PivotTo(object.PrimaryPart.CFrame:Lerp(calculateItemPosition(), speed))
- end
- end
- -- Returns the instant/non interpolated postition of the object at any given moment (non interpolated = not moving with the mouse but just tp'ing)
- local function getInstantCFrame()
- return calculateItemPosition()
- end
- function placement:place(remote)
- if not collided and object then
- -- Calculate the relative CFrame of the object to the plot
- local cf = plot.CFrame:Inverse() * calculateItemPosition() -- assuming `calculateItemPosition` provides world CFrame
- if not remote:InvokeServer(object.Name, placedObjects, cf, plot, plotModel) then
- placement.cancel(nil, nil, nil, true)
- return true
- else
- placement.cancel(nil, nil, nil, true)
- return true
- end
- else
- return false
- end
- end
- -- Check if model the will snap evenly on the plot, handling floating-point precision
- local function verifyPlane()
- local plotX = math.round(plot.Size.X) local plotZ = math.round(plot.Size.Z) return plotX % GRID_SIZE == 0 and plotZ % GRID_SIZE == 0
- end
- -- Confirms that the settings are valid for placement
- local function approvePlacement(id)
- local playerData = DataEvent:InvokeServer()
- if playerData.Inventory[id] == nil then
- warn("player has no model in inventory he can place")
- return false
- end
- 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
- warn("Grid size is too close to the size of the plot on at least one axis")
- return false
- 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
- -- activates placement (id = name, pobjs = placedOblects, plt = plotm, pltmdl = plot model, stk = stackable)
- function placement:activate(id, pobjs, plt, pltmdl, stk, player)
- -- Ensure that the object is destroyed if it already exists
- if object then
- object:Destroy()
- object = nil
- end
- -- Attempt to clone the new object from ITEM_LOCATION
- object = ITEM_LOCATION:FindFirstChild(id)
- if not object then
- warn("Item not found in ITEM_LOCATION: " .. id)
- return "Item not available for placement"
- end
- -- Clone and prepare the object for placement
- object = object:Clone()
- primary = object.PrimaryPart
- if not primary then
- warn("Primary part not found on object")
- return "Primary part missing"
- end
- placedObjects = pobjs
- plot = plt
- plotModel = pltmdl
- stackable = stk
- rotation = 0
- rotationVal = true
- collided = nil -- Reset collision state
- -- Ensure `CanCollide` is false for placement so it does not interfere with player
- for _, v in pairs(object:GetDescendants()) do
- if v:IsA("BasePart") then
- v.CanCollide = false
- end
- end
- -- Check if placement can activate
- if not approvePlacement(id) then
- warn("Placement could not activate")
- placement.cancel(nil, nil, nil, true) -- Call cancel to reset variables if placement cannot proceed
- return "Placement could not activate"
- end
- -- Set mouse target filter based on stackability
- if not stackable then
- mouse.TargetFilter = placedObjects
- else
- mouse.TargetFilter = object
- end
- -- Set interpolation speed
- local tempSpeed = interpolation and math.clamp(1 - lerpLevel, 0, 0.8) or 1
- speed = tempSpeed
- -- Render the grid and bind inputs
- renderGrid()
- object.Parent = placedObjects
- task.wait()
- bindInputs()
- runService:BindToRenderStep("Input", Enum.RenderPriority.Input.Value, translateObj)
- end
- return placement
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement