core: fix hotbar
This commit is contained in:
198
ServerScriptService/Actor/ClientState.lua
Normal file
198
ServerScriptService/Actor/ClientState.lua
Normal file
@@ -0,0 +1,198 @@
|
||||
--!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
|
||||
table.insert(blockCatalog, {
|
||||
id = tostring(id),
|
||||
name = block:GetAttribute("displayName") or block:GetAttribute("dn") or block.Name,
|
||||
})
|
||||
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
|
||||
@@ -2,15 +2,39 @@
|
||||
--!optimize 2
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local ClientStateService = require(script.Parent.ClientState)
|
||||
|
||||
local Shared = ReplicatedStorage:WaitForChild("Shared")
|
||||
local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
|
||||
local BlocksFolderRS = ReplicatedStorage:FindFirstChild("Blocks") or Instance.new("Folder")
|
||||
BlocksFolderRS.Name = "Blocks"
|
||||
BlocksFolderRS.Parent = ReplicatedStorage
|
||||
local BlocksFolderSS = ServerStorage:FindFirstChild("Blocks") or Instance.new("Folder")
|
||||
BlocksFolderSS.Name = "Blocks"
|
||||
BlocksFolderSS.Parent = ServerStorage
|
||||
|
||||
local Util = require(Shared.Util)
|
||||
local TG = require("./ServerChunkManager/TerrainGen")
|
||||
local TG = require(script.TerrainGen)
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local blockIdMap = {}
|
||||
local rebuildBlockIdMap
|
||||
|
||||
local function syncBlocksToServerStorage()
|
||||
BlocksFolderSS:ClearAllChildren()
|
||||
for _, child in ipairs(BlocksFolderRS:GetChildren()) do
|
||||
child:Clone().Parent = BlocksFolderSS
|
||||
end
|
||||
ClientStateService:SetBlocksFolder(BlocksFolderSS)
|
||||
if rebuildBlockIdMap then
|
||||
rebuildBlockIdMap()
|
||||
end
|
||||
end
|
||||
|
||||
BlocksFolderRS.ChildAdded:Connect(syncBlocksToServerStorage)
|
||||
BlocksFolderRS.ChildRemoved:Connect(syncBlocksToServerStorage)
|
||||
|
||||
do
|
||||
local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods")
|
||||
|
||||
@@ -22,6 +46,8 @@ end
|
||||
|
||||
local ML = require(Shared.ModLoader)
|
||||
ML.loadModsS()
|
||||
syncBlocksToServerStorage()
|
||||
ClientStateService:Init()
|
||||
|
||||
do
|
||||
local bv = Instance.new("BoolValue")
|
||||
@@ -67,7 +93,7 @@ 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 blocksFolder = BlocksFolderSS
|
||||
local function propogate(a, cx, cy, cz, x, y, z, bd)
|
||||
task.synchronize()
|
||||
tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd)
|
||||
@@ -75,9 +101,8 @@ local function propogate(a, cx, cy, cz, x, y, z, bd)
|
||||
end
|
||||
|
||||
local MAX_REACH = 512
|
||||
local blockIdMap = {}
|
||||
|
||||
local function rebuildBlockIdMap()
|
||||
rebuildBlockIdMap = function()
|
||||
table.clear(blockIdMap)
|
||||
for _, block in ipairs(blocksFolder:GetChildren()) do
|
||||
local id = block:GetAttribute("n")
|
||||
@@ -113,6 +138,17 @@ local function resolveBlockId(blockId: any): string | number | nil
|
||||
return blockIdMap[blockId]
|
||||
end
|
||||
|
||||
local function playerCanUseBlock(player: Player, resolvedId: any): boolean
|
||||
if not ClientStateService:HasInInventory(player, resolvedId) then
|
||||
return false
|
||||
end
|
||||
local selected = ClientStateService:GetSelectedBlockId(player)
|
||||
if not selected then
|
||||
return false
|
||||
end
|
||||
return tostring(selected) == tostring(resolvedId)
|
||||
end
|
||||
|
||||
local function getServerChunk(cx: number, cy: number, cz: number)
|
||||
task.desynchronize()
|
||||
local chunk = TG:GetChunk(cx, cy, cz)
|
||||
@@ -176,6 +212,9 @@ placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
|
||||
if not resolvedId then
|
||||
return reject("invalid id")
|
||||
end
|
||||
if not playerCanUseBlock(player, resolvedId) then
|
||||
return reject("not in inventory/hotbar")
|
||||
end
|
||||
|
||||
local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
|
||||
if isBlockInsidePlayer(blockPos) then
|
||||
|
||||
Reference in New Issue
Block a user