200 lines
4.5 KiB
Lua
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
|