codex: stuff
This commit is contained in:
8
.codex/AGENTS.md
Normal file
8
.codex/AGENTS.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
This project is a Minecraft-like voxel system for Roblox using Rojo. The server
|
||||||
|
is authoritative over chunks and blocks. Clients only handle input, UI, and
|
||||||
|
requests. Clients must never create or destroy blocks directly. The server must
|
||||||
|
validate distance, block type, and target. Shared modules must not reference
|
||||||
|
Roblox services.
|
||||||
|
|
||||||
|
Keep in mind that to replicate anything across the client-server model or to
|
||||||
|
write to any instance you must use serial luau
|
||||||
7
src/ReplicatedStorage/Remotes/BreakBlock.rbxmx
Normal file
7
src/ReplicatedStorage/Remotes/BreakBlock.rbxmx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<roblox version="4">
|
||||||
|
<Item class="RemoteEvent" referent="RBX0D1D3E8A8F5C4B0B9F0B123E18E5D6A2">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">BreakBlock</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
7
src/ReplicatedStorage/Remotes/PlaceBlock.rbxmx
Normal file
7
src/ReplicatedStorage/Remotes/PlaceBlock.rbxmx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<roblox version="4">
|
||||||
|
<Item class="RemoteEvent" referent="RBX3F7B7B3E7E5C4D8B9A9B3D8C76E8C0A1">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">PlaceBlock</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
4
src/ReplicatedStorage/Remotes/init.meta.json
Normal file
4
src/ReplicatedStorage/Remotes/init.meta.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"className": "Folder",
|
||||||
|
"ignoreUnknownInstances": true
|
||||||
|
}
|
||||||
@@ -39,17 +39,20 @@ local function propogateNeighboringBlockChanges(cx, cy, cz, x, y, z)
|
|||||||
local d = c.data[`{x},{y},{z}`]
|
local d = c.data[`{x},{y},{z}`]
|
||||||
if not d then return end
|
if not d then return end
|
||||||
|
|
||||||
task.synchronize()
|
|
||||||
|
|
||||||
if c:IsBlockRenderable(x, y, z) then
|
if c:IsBlockRenderable(x, y, z) then
|
||||||
if c.instance:FindFirstChild(`{x},{y},{z}`) then return end
|
if c.instance:FindFirstChild(`{x},{y},{z}`) then return end
|
||||||
|
task.synchronize()
|
||||||
local block = BlockManager:GetBlockRotated(d.id, util.RotationStringToNormalId(d.state["r"] or "f"), d.state)
|
local block = BlockManager:GetBlockRotated(d.id, util.RotationStringToNormalId(d.state["r"] or "f"), d.state)
|
||||||
block.Name = `{x},{y},{z}`
|
block.Name = `{x},{y},{z}`
|
||||||
block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z)))
|
block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z)))
|
||||||
block.Parent = c.instance
|
block.Parent = c.instance
|
||||||
|
task.desynchronize()
|
||||||
else
|
else
|
||||||
if c.instance:FindFirstChild(`{x},{y},{z}`) then
|
local existing = c.instance:FindFirstChild(`{x},{y},{z}`)
|
||||||
c.instance:FindFirstChild(`{x},{y},{z}`):Destroy()
|
if existing then
|
||||||
|
task.synchronize()
|
||||||
|
existing:Destroy()
|
||||||
|
task.desynchronize()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -28,6 +28,55 @@ local function findParent(i: Instance): Instance
|
|||||||
end
|
end
|
||||||
|
|
||||||
local Mouse: Mouse = nil
|
local Mouse: Mouse = nil
|
||||||
|
local lastNormalId: Enum.NormalId? = nil
|
||||||
|
|
||||||
|
local function normalIdToOffset(normal: Enum.NormalId): Vector3
|
||||||
|
if normal == Enum.NormalId.Top then
|
||||||
|
return Vector3.new(0, 1, 0)
|
||||||
|
elseif normal == Enum.NormalId.Bottom then
|
||||||
|
return Vector3.new(0, -1, 0)
|
||||||
|
elseif normal == Enum.NormalId.Left then
|
||||||
|
return Vector3.new(-1, 0, 0)
|
||||||
|
elseif normal == Enum.NormalId.Right then
|
||||||
|
return Vector3.new(1, 0, 0)
|
||||||
|
elseif normal == Enum.NormalId.Back then
|
||||||
|
return Vector3.new(0, 0, 1)
|
||||||
|
elseif normal == Enum.NormalId.Front then
|
||||||
|
return Vector3.new(0, 0, -1)
|
||||||
|
end
|
||||||
|
return Vector3.new(0, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3)
|
||||||
|
local cx, cy, cz = chunk.X, chunk.Y, chunk.Z
|
||||||
|
local bx, by, bz = block.X + offset.X, block.Y + offset.Y, block.Z + offset.Z
|
||||||
|
|
||||||
|
if bx < 1 then
|
||||||
|
bx = 8
|
||||||
|
cx -= 1
|
||||||
|
elseif bx > 8 then
|
||||||
|
bx = 1
|
||||||
|
cx += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if by < 1 then
|
||||||
|
by = 8
|
||||||
|
cy -= 1
|
||||||
|
elseif by > 8 then
|
||||||
|
by = 1
|
||||||
|
cy += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if bz < 1 then
|
||||||
|
bz = 8
|
||||||
|
cz -= 1
|
||||||
|
elseif bz > 8 then
|
||||||
|
bz = 1
|
||||||
|
cz += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz)
|
||||||
|
end
|
||||||
|
|
||||||
-- Gets the block and normalid of the block (and surface) the player is looking at
|
-- Gets the block and normalid of the block (and surface) the player is looking at
|
||||||
function PlacementManager:Raycast()
|
function PlacementManager:Raycast()
|
||||||
@@ -40,6 +89,7 @@ function PlacementManager:Raycast()
|
|||||||
if not objLookingAt then
|
if not objLookingAt then
|
||||||
PlacementManager.SelectionBox.Adornee = nil
|
PlacementManager.SelectionBox.Adornee = nil
|
||||||
script.RaycastResult.Value = nil
|
script.RaycastResult.Value = nil
|
||||||
|
lastNormalId = nil
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -48,10 +98,12 @@ function PlacementManager:Raycast()
|
|||||||
if parent:GetAttribute("ns") == true then
|
if parent:GetAttribute("ns") == true then
|
||||||
PlacementManager.SelectionBox.Adornee = nil
|
PlacementManager.SelectionBox.Adornee = nil
|
||||||
script.RaycastResult.Value = nil
|
script.RaycastResult.Value = nil
|
||||||
|
lastNormalId = nil
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
PlacementManager.SelectionBox.Adornee = parent
|
PlacementManager.SelectionBox.Adornee = parent
|
||||||
script.RaycastResult.Value = parent
|
script.RaycastResult.Value = parent
|
||||||
|
lastNormalId = dir
|
||||||
return parent, dir
|
return parent, dir
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -59,16 +111,17 @@ function PlacementManager:RaycastGetResult()
|
|||||||
return script.RaycastResult.Value
|
return script.RaycastResult.Value
|
||||||
end
|
end
|
||||||
|
|
||||||
local placeRemote = game:GetService("ReplicatedStorage").PlaceBlock
|
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
|
||||||
local breakRemote = game:GetService("ReplicatedStorage").BreakBlock
|
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
||||||
|
local breakRemote = remotes:WaitForChild("BreakBlock")
|
||||||
local tickRemote = game:GetService("ReplicatedStorage").Tick
|
local tickRemote = game:GetService("ReplicatedStorage").Tick
|
||||||
|
|
||||||
-- FIRES REMOTE
|
-- FIRES REMOTE
|
||||||
function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockData)
|
function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string)
|
||||||
--print("placeblock")
|
--print("placeblock")
|
||||||
--local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
--local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
||||||
--chunk:CreateBlock(x, y, z, blockData)
|
--chunk:CreateBlock(x, y, z, blockData)
|
||||||
placeRemote:FireServer(cx, cy, cz, x, y, z, blockData)
|
placeRemote:FireServer(cx, cy, cz, x, y, z, blockId)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- FIRES REMOTE
|
-- FIRES REMOTE
|
||||||
@@ -97,11 +150,13 @@ function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector
|
|||||||
if selectedPart == nil then
|
if selectedPart == nil then
|
||||||
PlacementManager.SelectionBox.Adornee = nil
|
PlacementManager.SelectionBox.Adornee = nil
|
||||||
script.RaycastResult.Value = nil
|
script.RaycastResult.Value = nil
|
||||||
|
lastNormalId = nil
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if not selectedPart.Parent then
|
if not selectedPart.Parent then
|
||||||
PlacementManager.SelectionBox.Adornee = nil
|
PlacementManager.SelectionBox.Adornee = nil
|
||||||
script.RaycastResult.Value = nil
|
script.RaycastResult.Value = nil
|
||||||
|
lastNormalId = nil
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local chunkCoords = Util.BlockPosStringToCoords(selectedPart.Parent.Name)
|
local chunkCoords = Util.BlockPosStringToCoords(selectedPart.Parent.Name)
|
||||||
@@ -114,6 +169,32 @@ function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function PlacementManager:GetTargetAtMouse(): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId}
|
||||||
|
local hit = PlacementManager:GetBlockAtMouse()
|
||||||
|
if not hit or not lastNormalId then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
chunk = hit.chunk,
|
||||||
|
block = hit.block,
|
||||||
|
normal = lastNormalId
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function PlacementManager:GetPlacementAtMouse(): nil | {chunk:Vector3, block: Vector3}
|
||||||
|
local hit = PlacementManager:GetTargetAtMouse()
|
||||||
|
if not hit then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local offset = normalIdToOffset(hit.normal)
|
||||||
|
local placeChunk, placeBlock = offsetChunkBlock(hit.chunk, hit.block, offset)
|
||||||
|
return {
|
||||||
|
chunk = placeChunk,
|
||||||
|
block = placeBlock
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
function PlacementManager:Init()
|
function PlacementManager:Init()
|
||||||
game:GetService("RunService").Heartbeat:Connect(function()
|
game:GetService("RunService").Heartbeat:Connect(function()
|
||||||
local a,b = pcall(function()
|
local a,b = pcall(function()
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ TerrainGen.ServerChunkCache = {} :: {[typeof("")]: typeof(Chunk.new(0,0,0))}
|
|||||||
|
|
||||||
-- Load a chunk from the DataStore or generate it if not found
|
-- Load a chunk from the DataStore or generate it if not found
|
||||||
function TerrainGen:GetChunk(x, y, z)
|
function TerrainGen:GetChunk(x, y, z)
|
||||||
|
local key = `{x},{y},{z}`
|
||||||
|
if TerrainGen.ServerChunkCache[key] then
|
||||||
|
return TerrainGen.ServerChunkCache[key]
|
||||||
|
end
|
||||||
|
|
||||||
-- Generate a new chunk if it doesn't exist
|
-- Generate a new chunk if it doesn't exist
|
||||||
local chunk = Chunk.new(x, y, z)
|
local chunk = Chunk.new(x, y, z)
|
||||||
@@ -38,6 +42,7 @@ function TerrainGen:GetChunk(x, y, z)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
TerrainGen.ServerChunkCache[key] = chunk
|
||||||
return chunk
|
return chunk
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|||||||
local Shared = ReplicatedStorage:WaitForChild("Shared")
|
local Shared = ReplicatedStorage:WaitForChild("Shared")
|
||||||
local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
|
local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
|
||||||
|
|
||||||
local PlacementManager = require(Shared.PlacementManager)
|
local Util = require(Shared.Util)
|
||||||
local TG = require("./ServerChunkManager/TerrainGen")
|
local TG = require("./ServerChunkManager/TerrainGen")
|
||||||
|
|
||||||
do
|
do
|
||||||
@@ -64,11 +64,67 @@ ReplicatedStorage.RecieveChunkPacket.OnServerInvoke = function(plr: Player, x: n
|
|||||||
end
|
end
|
||||||
|
|
||||||
local tickRemote = ReplicatedStorage.Tick
|
local tickRemote = ReplicatedStorage.Tick
|
||||||
|
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
||||||
|
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
||||||
|
local breakRemote = remotes:WaitForChild("BreakBlock")
|
||||||
|
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
||||||
local function propogate(a, cx, cy, cz, x, y, z, bd)
|
local function propogate(a, cx, cy, cz, x, y, z, bd)
|
||||||
|
task.synchronize()
|
||||||
tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd)
|
tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd)
|
||||||
|
task.desynchronize()
|
||||||
end
|
end
|
||||||
|
|
||||||
ReplicatedStorage.PlaceBlock.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockData)
|
local MAX_REACH = 24
|
||||||
|
local blockIdMap = {}
|
||||||
|
|
||||||
|
local function rebuildBlockIdMap()
|
||||||
|
table.clear(blockIdMap)
|
||||||
|
for _, block in ipairs(blocksFolder:GetChildren()) do
|
||||||
|
local id = block:GetAttribute("n")
|
||||||
|
if id ~= nil then
|
||||||
|
blockIdMap[id] = id
|
||||||
|
blockIdMap[tostring(id)] = id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rebuildBlockIdMap()
|
||||||
|
blocksFolder.ChildAdded:Connect(rebuildBlockIdMap)
|
||||||
|
blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap)
|
||||||
|
|
||||||
|
local function getPlayerPosition(player: Player): Vector3?
|
||||||
|
local character = player.Character
|
||||||
|
if not character then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local root = character:FindFirstChild("HumanoidRootPart")
|
||||||
|
if not root then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return root.Position
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
|
||||||
|
local playerPos = getPlayerPosition(player)
|
||||||
|
if not playerPos then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
|
||||||
|
return (blockPos - playerPos).Magnitude <= MAX_REACH
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resolveBlockId(blockId: any): string | number | nil
|
||||||
|
return blockIdMap[blockId]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getServerChunk(cx: number, cy: number, cz: number)
|
||||||
|
task.desynchronize()
|
||||||
|
local chunk = TG:GetChunk(cx, cy, cz)
|
||||||
|
task.synchronize()
|
||||||
|
return chunk
|
||||||
|
end
|
||||||
|
|
||||||
|
placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
|
||||||
--print("place",player, cx, cy, cz, x, y, z, blockData)
|
--print("place",player, cx, cy, cz, x, y, z, blockData)
|
||||||
|
|
||||||
if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
|
if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
|
||||||
@@ -77,16 +133,33 @@ ReplicatedStorage.PlaceBlock.OnServerEvent:Connect(function(player, cx, cy, cz,
|
|||||||
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
|
||||||
|
return
|
||||||
|
end
|
||||||
if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
|
if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
|
||||||
--return
|
--return
|
||||||
end
|
end
|
||||||
|
if not isWithinReach(player, cx, cy, cz, x, y, z) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local resolvedId = resolveBlockId(blockId)
|
||||||
|
if not resolvedId then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
--local chunk = TG:GetChunk(cx, cy, cz)
|
local chunk = getServerChunk(cx, cy, cz)
|
||||||
--PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, blockData)
|
if chunk:GetBlockAt(x, y, z) then
|
||||||
propogate("B_C", cx, cy, cz, x, y, z, blockData)
|
return
|
||||||
|
end
|
||||||
|
local data = {
|
||||||
|
id = resolvedId,
|
||||||
|
state = {}
|
||||||
|
}
|
||||||
|
chunk:CreateBlock(x, y, z, data)
|
||||||
|
propogate("B_C", cx, cy, cz, x, y, z, data)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
ReplicatedStorage.BreakBlock.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z)
|
breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z)
|
||||||
--print("del",player, cx, cy, cz, x, y, z)
|
--print("del",player, cx, cy, cz, x, y, z)
|
||||||
|
|
||||||
if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
|
if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
|
||||||
@@ -95,13 +168,22 @@ ReplicatedStorage.BreakBlock.OnServerEvent:Connect(function(player, cx, cy, cz,
|
|||||||
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
|
||||||
|
return
|
||||||
|
end
|
||||||
if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
|
if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if not isWithinReach(player, cx, cy, cz, x, y, z) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
--local chunk = TG:GetChunk(cx, cy, cz)
|
local chunk = getServerChunk(cx, cy, cz)
|
||||||
--PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z)
|
if not chunk:GetBlockAt(x, y, z) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
chunk:RemoveBlock(x, y, z)
|
||||||
propogate("B_D", cx, cy, cz, x, y, z, 0)
|
propogate("B_D", cx, cy, cz, x, y, z, 0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
task.desynchronize()
|
task.desynchronize()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ end
|
|||||||
local ui = script.Parent
|
local ui = script.Parent
|
||||||
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local UIS = game:GetService("UserInputService")
|
|
||||||
|
|
||||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||||
|
|
||||||
@@ -18,22 +17,6 @@ cd.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
|||||||
sky.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
sky.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
||||||
base.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
base.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
||||||
|
|
||||||
local PM = require(ReplicatedStorage.Shared.PlacementManager)
|
|
||||||
|
|
||||||
UIS.InputEnded:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
|
||||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
|
||||||
local mouseBlock = PM:GetBlockAtMouse()
|
|
||||||
if not mouseBlock then return end
|
|
||||||
PM:BreakBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z)
|
|
||||||
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
|
|
||||||
local mouseBlock = PM:GetBlockAtMouse()
|
|
||||||
if not mouseBlock then return end
|
|
||||||
PM:PlaceBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z, {
|
|
||||||
id = 2,
|
|
||||||
state = {}
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
game:GetService("RunService").RenderStepped:Connect(function(dt)
|
game:GetService("RunService").RenderStepped:Connect(function(dt)
|
||||||
local fps = math.round(1/dt)
|
local fps = math.round(1/dt)
|
||||||
@@ -76,4 +59,4 @@ game:GetService("RunService").RenderStepped:Connect(function(dt)
|
|||||||
)
|
)
|
||||||
|
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
if not game:IsLoaded() then
|
||||||
|
game.Loaded:Wait()
|
||||||
|
end
|
||||||
|
|
||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local UIS = game:GetService("UserInputService")
|
||||||
|
|
||||||
|
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||||
|
|
||||||
|
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
||||||
|
local PM = require(ReplicatedStorage.Shared.PlacementManager)
|
||||||
|
|
||||||
|
local HOTBAR_SIZE = 9
|
||||||
|
local hotbar = table.create(HOTBAR_SIZE)
|
||||||
|
local selectedSlot = 1
|
||||||
|
|
||||||
|
local keyToSlot = {
|
||||||
|
[Enum.KeyCode.One] = 1,
|
||||||
|
[Enum.KeyCode.Two] = 2,
|
||||||
|
[Enum.KeyCode.Three] = 3,
|
||||||
|
[Enum.KeyCode.Four] = 4,
|
||||||
|
[Enum.KeyCode.Five] = 5,
|
||||||
|
[Enum.KeyCode.Six] = 6,
|
||||||
|
[Enum.KeyCode.Seven] = 7,
|
||||||
|
[Enum.KeyCode.Eight] = 8,
|
||||||
|
[Enum.KeyCode.Nine] = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function rebuildHotbar()
|
||||||
|
local ids = {}
|
||||||
|
for _, block in ipairs(blocksFolder:GetChildren()) do
|
||||||
|
local id = block:GetAttribute("n")
|
||||||
|
if id ~= nil then
|
||||||
|
table.insert(ids, tostring(id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(ids)
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
hotbar[i] = ids[i] or ""
|
||||||
|
end
|
||||||
|
selectedSlot = math.clamp(selectedSlot, 1, HOTBAR_SIZE)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getSelectedBlockId(): string?
|
||||||
|
local id = hotbar[selectedSlot]
|
||||||
|
if id == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setSelectedSlot(slot: number)
|
||||||
|
if slot < 1 or slot > HOTBAR_SIZE then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
selectedSlot = slot
|
||||||
|
end
|
||||||
|
|
||||||
|
rebuildHotbar()
|
||||||
|
blocksFolder.ChildAdded:Connect(rebuildHotbar)
|
||||||
|
blocksFolder.ChildRemoved:Connect(rebuildHotbar)
|
||||||
|
|
||||||
|
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
||||||
|
if gameProcessedEvent then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local slot = keyToSlot[input.KeyCode]
|
||||||
|
if slot then
|
||||||
|
setSelectedSlot(slot)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||||
|
local mouseBlock = PM:GetBlockAtMouse()
|
||||||
|
if not mouseBlock then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
PM:BreakBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z)
|
||||||
|
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
|
||||||
|
local mouseBlock = PM:GetPlacementAtMouse()
|
||||||
|
if not mouseBlock then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local blockId = getSelectedBlockId()
|
||||||
|
if not blockId then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
PM:PlaceBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z, blockId)
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -3,6 +3,7 @@ if not game:IsLoaded() then
|
|||||||
end
|
end
|
||||||
|
|
||||||
pcall(function()
|
pcall(function()
|
||||||
|
task.synchronize()
|
||||||
game:GetService("Workspace"):WaitForChild("$blockscraft_server",5):Destroy()
|
game:GetService("Workspace"):WaitForChild("$blockscraft_server",5):Destroy()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -22,4 +23,4 @@ end
|
|||||||
do
|
do
|
||||||
local CM = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ChunkManager"))
|
local CM = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ChunkManager"))
|
||||||
CM:Init()
|
CM:Init()
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user