codex: stuff
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
This project is a Minecraft-like voxel system for Roblox using Rojo. The server
|
# Agents
|
||||||
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
|
- This project is a Minecraft-like voxel system for Roblox using Rojo.
|
||||||
write to any instance you must use serial luau
|
- 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.
|
||||||
|
|
||||||
|
Note: To replicate anything across the client-server model or to write to any
|
||||||
|
instance you must use serial Luau.
|
||||||
|
|
||||||
|
The main AGENTS.md file is in the `.codex` folder!
|
||||||
|
|||||||
7
src/ReplicatedStorage/Remotes/InventoryRequest.rbxmx
Normal file
7
src/ReplicatedStorage/Remotes/InventoryRequest.rbxmx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<roblox version="4">
|
||||||
|
<Item class="RemoteEvent" referent="RBX5B7E5C8D4E2348B78F8C1A2E9E6D4F90">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">InventoryRequest</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
7
src/ReplicatedStorage/Remotes/InventorySync.rbxmx
Normal file
7
src/ReplicatedStorage/Remotes/InventorySync.rbxmx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<roblox version="4">
|
||||||
|
<Item class="RemoteEvent" referent="RBX7A1C0A5FA0E0412F9B2F5A43B9C4B3A1">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">InventorySync</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
32
src/ReplicatedStorage/Shared/Catppuccin.lua
Normal file
32
src/ReplicatedStorage/Shared/Catppuccin.lua
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
local Catppuccin = {}
|
||||||
|
|
||||||
|
Catppuccin.mocha = {
|
||||||
|
rosewater = Color3.fromRGB(245, 224, 220),
|
||||||
|
flamingo = Color3.fromRGB(242, 205, 205),
|
||||||
|
pink = Color3.fromRGB(245, 194, 231),
|
||||||
|
base = Color3.fromRGB(30, 30, 46),
|
||||||
|
mantle = Color3.fromRGB(24, 24, 37),
|
||||||
|
crust = Color3.fromRGB(17, 17, 27),
|
||||||
|
surface0 = Color3.fromRGB(49, 50, 68),
|
||||||
|
surface1 = Color3.fromRGB(69, 71, 90),
|
||||||
|
surface2 = Color3.fromRGB(88, 91, 112),
|
||||||
|
overlay0 = Color3.fromRGB(108, 112, 134),
|
||||||
|
overlay1 = Color3.fromRGB(127, 132, 156),
|
||||||
|
overlay2 = Color3.fromRGB(147, 153, 178),
|
||||||
|
text = Color3.fromRGB(205, 214, 244),
|
||||||
|
subtext0 = Color3.fromRGB(166, 173, 200),
|
||||||
|
subtext1 = Color3.fromRGB(186, 194, 222),
|
||||||
|
mauve = Color3.fromRGB(203, 166, 247),
|
||||||
|
red = Color3.fromRGB(243, 139, 168),
|
||||||
|
maroon = Color3.fromRGB(235, 160, 172),
|
||||||
|
peach = Color3.fromRGB(250, 179, 135),
|
||||||
|
yellow = Color3.fromRGB(249, 226, 175),
|
||||||
|
green = Color3.fromRGB(166, 227, 161),
|
||||||
|
teal = Color3.fromRGB(148, 226, 213),
|
||||||
|
sky = Color3.fromRGB(137, 220, 235),
|
||||||
|
sapphire = Color3.fromRGB(116, 199, 236),
|
||||||
|
blue = Color3.fromRGB(137, 180, 250),
|
||||||
|
lavender = Color3.fromRGB(180, 190, 254),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Catppuccin
|
||||||
106
src/ReplicatedStorage/Shared/Inventory.lua
Normal file
106
src/ReplicatedStorage/Shared/Inventory.lua
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
local Inventory = {}
|
||||||
|
|
||||||
|
local HOTBAR_SIZE = 9
|
||||||
|
local slots = table.create(HOTBAR_SIZE)
|
||||||
|
local selectedIndex = 1
|
||||||
|
|
||||||
|
local listeners = {
|
||||||
|
changed = {},
|
||||||
|
selected = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function addListener(list, fn)
|
||||||
|
table.insert(list, fn)
|
||||||
|
local active = true
|
||||||
|
return {
|
||||||
|
Disconnect = function()
|
||||||
|
if not active then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
active = false
|
||||||
|
for i, cb in ipairs(list) do
|
||||||
|
if cb == fn then
|
||||||
|
table.remove(list, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function notify(list, ...)
|
||||||
|
for _, cb in ipairs(list) do
|
||||||
|
cb(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.GetHotbarSize(): number
|
||||||
|
return HOTBAR_SIZE
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.GetSlots(): {string?}
|
||||||
|
return slots
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.GetSlot(index: number): string?
|
||||||
|
return slots[index]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.SetSlots(ids: {string?})
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
local value = ids[i]
|
||||||
|
if value == "" then
|
||||||
|
value = nil
|
||||||
|
end
|
||||||
|
slots[i] = value
|
||||||
|
end
|
||||||
|
selectedIndex = math.clamp(selectedIndex, 1, HOTBAR_SIZE)
|
||||||
|
if slots[selectedIndex] == nil then
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
if slots[i] ~= nil then
|
||||||
|
selectedIndex = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
notify(listeners.changed, slots)
|
||||||
|
notify(listeners.selected, selectedIndex, Inventory.GetSelectedId())
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.SetSlot(index: number, id: string?)
|
||||||
|
if index < 1 or index > HOTBAR_SIZE then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
slots[index] = id
|
||||||
|
notify(listeners.changed, slots)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.GetSelectedIndex(): number
|
||||||
|
return selectedIndex
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.SetSelectedIndex(index: number)
|
||||||
|
if index < 1 or index > HOTBAR_SIZE then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if selectedIndex == index then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
selectedIndex = index
|
||||||
|
notify(listeners.selected, selectedIndex, Inventory.GetSelectedId())
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.GetSelectedId(): string?
|
||||||
|
local id = slots[selectedIndex]
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.OnChanged(callback)
|
||||||
|
return addListener(listeners.changed, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Inventory.OnSelected(callback)
|
||||||
|
return addListener(listeners.selected, callback)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Inventory
|
||||||
@@ -67,6 +67,8 @@ local tickRemote = ReplicatedStorage.Tick
|
|||||||
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
||||||
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
||||||
local breakRemote = remotes:WaitForChild("BreakBlock")
|
local breakRemote = remotes:WaitForChild("BreakBlock")
|
||||||
|
local inventorySync = remotes:WaitForChild("InventorySync")
|
||||||
|
local inventoryRequest = remotes:WaitForChild("InventoryRequest")
|
||||||
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
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()
|
task.synchronize()
|
||||||
@@ -75,7 +77,9 @@ local function propogate(a, cx, cy, cz, x, y, z, bd)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local MAX_REACH = 24
|
local MAX_REACH = 24
|
||||||
|
local HOTBAR_SIZE = 9
|
||||||
local blockIdMap = {}
|
local blockIdMap = {}
|
||||||
|
local playerInventories = {}
|
||||||
|
|
||||||
local function rebuildBlockIdMap()
|
local function rebuildBlockIdMap()
|
||||||
table.clear(blockIdMap)
|
table.clear(blockIdMap)
|
||||||
@@ -92,6 +96,94 @@ rebuildBlockIdMap()
|
|||||||
blocksFolder.ChildAdded:Connect(rebuildBlockIdMap)
|
blocksFolder.ChildAdded:Connect(rebuildBlockIdMap)
|
||||||
blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap)
|
blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap)
|
||||||
|
|
||||||
|
local function buildDefaultSlots(): {string}
|
||||||
|
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, function(a, b)
|
||||||
|
return a < b
|
||||||
|
end)
|
||||||
|
local slots = table.create(HOTBAR_SIZE, "")
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
slots[i] = ids[i]
|
||||||
|
end
|
||||||
|
return slots
|
||||||
|
end
|
||||||
|
|
||||||
|
local function syncInventory(player: Player)
|
||||||
|
local data = playerInventories[player.UserId]
|
||||||
|
if not data then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
task.synchronize()
|
||||||
|
inventorySync:FireClient(player, data.slots)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rebuildAllInventories()
|
||||||
|
for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
|
||||||
|
local slots = buildDefaultSlots()
|
||||||
|
local allowed = {}
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
local id = slots[i]
|
||||||
|
if id ~= "" then
|
||||||
|
allowed[id] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
playerInventories[player.UserId] = {
|
||||||
|
slots = slots,
|
||||||
|
allowed = allowed,
|
||||||
|
}
|
||||||
|
syncInventory(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
blocksFolder.ChildAdded:Connect(rebuildAllInventories)
|
||||||
|
blocksFolder.ChildRemoved:Connect(rebuildAllInventories)
|
||||||
|
|
||||||
|
game:GetService("Players").PlayerAdded:Connect(function(player: Player)
|
||||||
|
local slots = buildDefaultSlots()
|
||||||
|
local allowed = {}
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
local id = slots[i]
|
||||||
|
if id ~= "" then
|
||||||
|
allowed[id] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
playerInventories[player.UserId] = {
|
||||||
|
slots = slots,
|
||||||
|
allowed = allowed,
|
||||||
|
}
|
||||||
|
syncInventory(player)
|
||||||
|
end)
|
||||||
|
|
||||||
|
game:GetService("Players").PlayerRemoving:Connect(function(player: Player)
|
||||||
|
playerInventories[player.UserId] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
|
||||||
|
local slots = buildDefaultSlots()
|
||||||
|
local allowed = {}
|
||||||
|
for i = 1, HOTBAR_SIZE do
|
||||||
|
local id = slots[i]
|
||||||
|
if id ~= "" then
|
||||||
|
allowed[id] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
playerInventories[player.UserId] = {
|
||||||
|
slots = slots,
|
||||||
|
allowed = allowed,
|
||||||
|
}
|
||||||
|
syncInventory(player)
|
||||||
|
end
|
||||||
|
|
||||||
|
inventoryRequest.OnServerEvent:Connect(function(player: Player)
|
||||||
|
syncInventory(player)
|
||||||
|
end)
|
||||||
|
|
||||||
local function getPlayerPosition(player: Player): Vector3?
|
local function getPlayerPosition(player: Player): Vector3?
|
||||||
local character = player.Character
|
local character = player.Character
|
||||||
if not character then
|
if not character then
|
||||||
@@ -117,6 +209,14 @@ local function resolveBlockId(blockId: any): string | number | nil
|
|||||||
return blockIdMap[blockId]
|
return blockIdMap[blockId]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function playerHasBlockId(player: Player, blockId: string | number): boolean
|
||||||
|
local data = playerInventories[player.UserId]
|
||||||
|
if not data then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return data.allowed[tostring(blockId)] == true
|
||||||
|
end
|
||||||
|
|
||||||
local function getServerChunk(cx: number, cy: number, cz: number)
|
local function getServerChunk(cx: number, cy: number, cz: number)
|
||||||
task.desynchronize()
|
task.desynchronize()
|
||||||
local chunk = TG:GetChunk(cx, cy, cz)
|
local chunk = TG:GetChunk(cx, cy, cz)
|
||||||
@@ -146,6 +246,9 @@ placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
|
|||||||
if not resolvedId then
|
if not resolvedId then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if not playerHasBlockId(player, resolvedId) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local chunk = getServerChunk(cx, cy, cz)
|
local chunk = getServerChunk(cx, cy, cz)
|
||||||
if chunk:GetBlockAt(x, y, z) then
|
if chunk:GetBlockAt(x, y, z) then
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ end
|
|||||||
local ui = script.Parent
|
local ui = script.Parent
|
||||||
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
local Inventory = require(ReplicatedStorage.Shared.Inventory)
|
||||||
|
local Catppuccin = require(ReplicatedStorage.Shared.Catppuccin)
|
||||||
|
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
||||||
|
|
||||||
|
local mocha = Catppuccin.mocha
|
||||||
|
|
||||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||||
|
|
||||||
@@ -17,6 +22,165 @@ 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 hotbarRoot = ui:WaitForChild("Hotbar")
|
||||||
|
local hotbarSlotsRoot = hotbarRoot:WaitForChild("Frame")
|
||||||
|
local hotbarSelectLabel = ui:WaitForChild("HotbarItemSelectLabel")
|
||||||
|
local hotbarSelectText = hotbarSelectLabel:WaitForChild("TextLabel")
|
||||||
|
local hotbarDebug = ui:WaitForChild("HotbarDebug")
|
||||||
|
local hotbarDebugText = hotbarDebug:WaitForChild("TextLabel")
|
||||||
|
local debugUpperText = ui:WaitForChild("DebugUpperText")
|
||||||
|
local hotbarStroke = hotbarRoot:FindFirstChild("UIStroke")
|
||||||
|
local hotbarSelectStroke = hotbarSelectLabel:FindFirstChild("UIStroke")
|
||||||
|
local hotbarDebugStroke = hotbarDebug:FindFirstChild("UIStroke")
|
||||||
|
|
||||||
|
hotbarRoot.BackgroundColor3 = mocha.base
|
||||||
|
if hotbarStroke then
|
||||||
|
hotbarStroke.Color = mocha.blue
|
||||||
|
end
|
||||||
|
hotbarSelectLabel.BackgroundColor3 = mocha.base
|
||||||
|
if hotbarSelectStroke then
|
||||||
|
hotbarSelectStroke.Color = mocha.blue
|
||||||
|
end
|
||||||
|
hotbarDebug.BackgroundColor3 = mocha.base
|
||||||
|
if hotbarDebugStroke then
|
||||||
|
hotbarDebugStroke.Color = mocha.surface2
|
||||||
|
end
|
||||||
|
hotbarSelectText.TextColor3 = mocha.text
|
||||||
|
hotbarDebugText.TextColor3 = mocha.text
|
||||||
|
debugUpperText.TextColor3 = mocha.text
|
||||||
|
|
||||||
|
local slotFrames = {}
|
||||||
|
local slotStrokes = {}
|
||||||
|
local slotLabels = {}
|
||||||
|
local renderHotbar = nil
|
||||||
|
|
||||||
|
local blockDisplayNames = {}
|
||||||
|
local blockIcons = {}
|
||||||
|
|
||||||
|
local function rebuildBlockMappings()
|
||||||
|
table.clear(blockDisplayNames)
|
||||||
|
table.clear(blockIcons)
|
||||||
|
for _, block in ipairs(blocksFolder:GetChildren()) do
|
||||||
|
local id = block:GetAttribute("n")
|
||||||
|
if id ~= nil then
|
||||||
|
blockDisplayNames[tostring(id)] = block.Name
|
||||||
|
local icon = block:GetAttribute("icon")
|
||||||
|
if typeof(icon) == "string" and icon ~= "" then
|
||||||
|
blockIcons[tostring(id)] = icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if renderHotbar then
|
||||||
|
renderHotbar()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rebuildBlockMappings()
|
||||||
|
blocksFolder.ChildAdded:Connect(rebuildBlockMappings)
|
||||||
|
blocksFolder.ChildRemoved:Connect(rebuildBlockMappings)
|
||||||
|
|
||||||
|
for i = 1, Inventory.GetHotbarSize() do
|
||||||
|
local slotName = `HotbarSlot{i - 1}`
|
||||||
|
local slot = hotbarSlotsRoot:WaitForChild(slotName)
|
||||||
|
slotFrames[i] = slot
|
||||||
|
local stroke = slot:FindFirstChild("UIStroke")
|
||||||
|
if not stroke then
|
||||||
|
stroke = Instance.new("UIStroke")
|
||||||
|
stroke.Parent = slot
|
||||||
|
end
|
||||||
|
slotStrokes[i] = stroke
|
||||||
|
|
||||||
|
local imageLabel = slot:FindFirstChild("ImageLabel")
|
||||||
|
if not imageLabel then
|
||||||
|
imageLabel = Instance.new("ImageLabel")
|
||||||
|
imageLabel.Name = "ImageLabel"
|
||||||
|
imageLabel.BackgroundTransparency = 1
|
||||||
|
imageLabel.Size = UDim2.fromScale(1, 1)
|
||||||
|
imageLabel.ScaleType = Enum.ScaleType.Fit
|
||||||
|
imageLabel.Parent = slot
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = slot:FindFirstChild("TextLabel")
|
||||||
|
if not label then
|
||||||
|
label = Instance.new("TextLabel")
|
||||||
|
label.Name = "TextLabel"
|
||||||
|
label.BackgroundTransparency = 1
|
||||||
|
label.Size = UDim2.fromScale(1, 1)
|
||||||
|
label.TextScaled = true
|
||||||
|
label.Font = Enum.Font.Gotham
|
||||||
|
label.TextColor3 = mocha.text
|
||||||
|
label.TextWrapped = true
|
||||||
|
label.ZIndex = 3
|
||||||
|
label.Parent = slot
|
||||||
|
end
|
||||||
|
slotLabels[i] = label
|
||||||
|
end
|
||||||
|
|
||||||
|
renderHotbar = function()
|
||||||
|
for i = 1, Inventory.GetHotbarSize() do
|
||||||
|
local slot = slotFrames[i]
|
||||||
|
if slot then
|
||||||
|
slot.BackgroundColor3 = mocha.surface0
|
||||||
|
local stroke = slotStrokes[i]
|
||||||
|
if stroke then
|
||||||
|
if i == Inventory.GetSelectedIndex() then
|
||||||
|
stroke.Color = mocha.blue
|
||||||
|
stroke.Thickness = 2
|
||||||
|
else
|
||||||
|
stroke.Color = mocha.surface2
|
||||||
|
stroke.Thickness = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local imageLabel = slot:FindFirstChild("ImageLabel")
|
||||||
|
local id = Inventory.GetSlot(i)
|
||||||
|
local displayName = id and (blockDisplayNames[tostring(id)] or tostring(id)) or ""
|
||||||
|
if imageLabel then
|
||||||
|
local icon = id and blockIcons[tostring(id)] or nil
|
||||||
|
imageLabel.Visible = icon ~= nil
|
||||||
|
imageLabel.Image = icon or ""
|
||||||
|
imageLabel.BackgroundTransparency = 1
|
||||||
|
imageLabel.ZIndex = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local textLabel = slotLabels[i]
|
||||||
|
if textLabel then
|
||||||
|
textLabel.Text = displayName
|
||||||
|
textLabel.TextColor3 = mocha.text
|
||||||
|
textLabel.Visible = displayName ~= ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function updateSelectedLabel()
|
||||||
|
local id = Inventory.GetSelectedId()
|
||||||
|
local displayName = id and (blockDisplayNames[tostring(id)] or tostring(id)) or nil
|
||||||
|
if hotbarSelectText then
|
||||||
|
hotbarSelectText.Text = displayName and `Selected: {displayName}` or "Selected: (empty)"
|
||||||
|
end
|
||||||
|
if hotbarDebugText then
|
||||||
|
hotbarDebugText.Text = `Slot {Inventory.GetSelectedIndex()} Id {displayName or "empty"}`
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local extraSlot = hotbarSlotsRoot and hotbarSlotsRoot:FindFirstChild("HotbarSlot9")
|
||||||
|
if extraSlot then
|
||||||
|
extraSlot.Visible = false
|
||||||
|
end
|
||||||
|
|
||||||
|
renderHotbar()
|
||||||
|
updateSelectedLabel()
|
||||||
|
|
||||||
|
Inventory.OnChanged(function()
|
||||||
|
renderHotbar()
|
||||||
|
updateSelectedLabel()
|
||||||
|
end)
|
||||||
|
|
||||||
|
Inventory.OnSelected(function()
|
||||||
|
renderHotbar()
|
||||||
|
updateSelectedLabel()
|
||||||
|
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)
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ local UIS = game:GetService("UserInputService")
|
|||||||
|
|
||||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||||
|
|
||||||
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
|
||||||
local PM = require(ReplicatedStorage.Shared.PlacementManager)
|
local PM = require(ReplicatedStorage.Shared.PlacementManager)
|
||||||
|
local Inventory = require(ReplicatedStorage.Shared.Inventory)
|
||||||
local HOTBAR_SIZE = 9
|
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
||||||
local hotbar = table.create(HOTBAR_SIZE)
|
local inventorySync = remotes:WaitForChild("InventorySync")
|
||||||
local selectedSlot = 1
|
local inventoryRequest = remotes:WaitForChild("InventoryRequest")
|
||||||
|
|
||||||
local keyToSlot = {
|
local keyToSlot = {
|
||||||
[Enum.KeyCode.One] = 1,
|
[Enum.KeyCode.One] = 1,
|
||||||
@@ -26,40 +25,25 @@ local keyToSlot = {
|
|||||||
[Enum.KeyCode.Nine] = 9,
|
[Enum.KeyCode.Nine] = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function rebuildHotbar()
|
local function findNextFilledIndex(startIndex: number, direction: number): number
|
||||||
local ids = {}
|
local size = Inventory.GetHotbarSize()
|
||||||
for _, block in ipairs(blocksFolder:GetChildren()) do
|
for step = 1, size do
|
||||||
local id = block:GetAttribute("n")
|
local idx = ((startIndex - 1 + (direction * step)) % size) + 1
|
||||||
if id ~= nil then
|
if Inventory.GetSlot(idx) ~= nil then
|
||||||
table.insert(ids, tostring(id))
|
return idx
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return startIndex
|
||||||
table.sort(ids)
|
|
||||||
for i = 1, HOTBAR_SIZE do
|
|
||||||
hotbar[i] = ids[i] or ""
|
|
||||||
end
|
|
||||||
selectedSlot = math.clamp(selectedSlot, 1, HOTBAR_SIZE)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getSelectedBlockId(): string?
|
inventorySync.OnClientEvent:Connect(function(slots)
|
||||||
local id = hotbar[selectedSlot]
|
if typeof(slots) ~= "table" then
|
||||||
if id == "" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
local function setSelectedSlot(slot: number)
|
|
||||||
if slot < 1 or slot > HOTBAR_SIZE then
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
selectedSlot = slot
|
Inventory.SetSlots(slots)
|
||||||
end
|
end)
|
||||||
|
|
||||||
rebuildHotbar()
|
inventoryRequest:FireServer()
|
||||||
blocksFolder.ChildAdded:Connect(rebuildHotbar)
|
|
||||||
blocksFolder.ChildRemoved:Connect(rebuildHotbar)
|
|
||||||
|
|
||||||
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
||||||
if gameProcessedEvent then
|
if gameProcessedEvent then
|
||||||
@@ -68,7 +52,9 @@ UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
|||||||
|
|
||||||
local slot = keyToSlot[input.KeyCode]
|
local slot = keyToSlot[input.KeyCode]
|
||||||
if slot then
|
if slot then
|
||||||
setSelectedSlot(slot)
|
if Inventory.GetSlot(slot) ~= nil then
|
||||||
|
Inventory.SetSelectedIndex(slot)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -83,10 +69,25 @@ UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
|||||||
if not mouseBlock then
|
if not mouseBlock then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local blockId = getSelectedBlockId()
|
local blockId = Inventory.GetSelectedId()
|
||||||
if not blockId then
|
if not blockId then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
PM:PlaceBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z, blockId)
|
PM:PlaceBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z, blockId)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
UIS.InputChanged:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
||||||
|
if gameProcessedEvent then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if input.UserInputType == Enum.UserInputType.MouseWheel then
|
||||||
|
local delta = input.Position.Z
|
||||||
|
if delta == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local direction = delta > 0 and -1 or 1
|
||||||
|
local nextIndex = findNextFilledIndex(Inventory.GetSelectedIndex(), direction)
|
||||||
|
Inventory.SetSelectedIndex(nextIndex)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user