Files
block-game/ServerScriptService/Actor/ClientState.lua
2026-01-09 17:02:35 +02:00

200 lines
4.5 KiB
Lua

--!native
--!optimize 2
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Replica = require(ReplicatedStorage.Packages.replica)
local ClientStateService = {}
local HOTBAR_SIZE = 10
local token = Replica.Token("ClientState")
local blockCatalog = {}
local playerReplicas = {} :: {[Player]: any}
local blocksFolder: Folder? = nil
local readyConnections = {} :: {[Player]: RBXScriptConnection}
local function sortBlocks()
table.sort(blockCatalog, function(a, b)
local na = tonumber(a.id)
local nb = tonumber(b.id)
if na and nb then
return na < nb
end
if na then
return true
end
if nb then
return false
end
return a.id < b.id
end)
end
local function rebuildBlockCatalog()
table.clear(blockCatalog)
if not blocksFolder then
return
end
for _, block in ipairs(blocksFolder:GetChildren()) do
local id = block:GetAttribute("n")
if id ~= nil then
local displayName = block:GetAttribute("name") or block:GetAttribute("displayName") or block:GetAttribute("dn") or block.Name
table.insert(blockCatalog, {
id = tostring(id),
name = displayName,
})
end
end
sortBlocks()
end
local function makeBaseState()
local inventory = {}
local hotbar = {}
for _, entry in ipairs(blockCatalog) do
inventory[entry.id] = {
name = entry.name,
count = 999999,
}
if #hotbar < HOTBAR_SIZE then
table.insert(hotbar, entry.id)
end
end
return {
inventory = inventory,
hotbar = hotbar,
selectedSlot = #hotbar > 0 and 1 or 0,
}
end
local function sanitizeSelection(hotbar, selectedSlot)
if type(selectedSlot) ~= "number" then
return (#hotbar > 0) and 1 or 0
end
if selectedSlot < 1 or selectedSlot > HOTBAR_SIZE then
return (#hotbar > 0) and 1 or 0
end
if not hotbar[selectedSlot] then
return (#hotbar > 0) and 1 or 0
end
return selectedSlot
end
local function refreshReplica(replica)
local state = makeBaseState()
replica:Set({"inventory"}, state.inventory)
replica:Set({"hotbar"}, state.hotbar)
replica:Set({"selectedSlot"}, sanitizeSelection(state.hotbar, replica.Data.selectedSlot))
end
function ClientStateService:SetBlocksFolder(folder: Folder?)
blocksFolder = folder
rebuildBlockCatalog()
for _, replica in pairs(playerReplicas) do
refreshReplica(replica)
end
end
function ClientStateService:GetReplica(player: Player)
return playerReplicas[player]
end
function ClientStateService:GetSelectedBlockId(player: Player)
local replica = playerReplicas[player]
if not replica then
return nil
end
local data = replica.Data
local hotbar = data.hotbar or {}
local selectedSlot = sanitizeSelection(hotbar, data.selectedSlot)
return hotbar[selectedSlot]
end
function ClientStateService:HasInInventory(player: Player, blockId: any): boolean
local replica = playerReplicas[player]
if not replica or not blockId then
return false
end
local inv = replica.Data.inventory
return inv and inv[tostring(blockId)] ~= nil or false
end
local function handleReplicaEvents(player: Player, replica)
replica.OnServerEvent:Connect(function(plr, action, payload)
if plr ~= player then
return
end
if action == "SelectHotbarSlot" then
local slot = tonumber(payload)
local hotbar = replica.Data.hotbar
if not hotbar then
return
end
if slot and slot >= 1 and slot <= HOTBAR_SIZE and hotbar[slot] then
replica:Set({"selectedSlot"}, slot)
end
end
end)
end
local function onPlayerAdded(player: Player)
local replica = Replica.New({
Token = token,
Tags = {
UserId = player.UserId,
Player = player,
},
Data = makeBaseState(),
})
if Replica.ReadyPlayers[player] then
replica:Subscribe(player)
else
readyConnections[player] = Replica.NewReadyPlayer:Connect(function(newPlayer)
if newPlayer ~= player then
return
end
if readyConnections[player] then
readyConnections[player]:Disconnect()
readyConnections[player] = nil
end
replica:Subscribe(player)
end)
end
handleReplicaEvents(player, replica)
playerReplicas[player] = replica
end
local function onPlayerRemoving(player: Player)
local replica = playerReplicas[player]
if replica then
replica:Destroy()
playerReplicas[player] = nil
end
if readyConnections[player] then
readyConnections[player]:Disconnect()
readyConnections[player] = nil
end
end
function ClientStateService:Init()
rebuildBlockCatalog()
for _, player in ipairs(Players:GetPlayers()) do
onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
end
return ClientStateService