Minecraft-like Roblox block game
rblx.games/135624152691584
roblox
roblox-game
rojo
1--!native
2--!optimize 2
3
4local Players = game:GetService("Players")
5local ReplicatedStorage = game:GetService("ReplicatedStorage")
6
7local Replica = require(ReplicatedStorage.Packages.replica)
8
9local ClientStateService = {}
10
11local HOTBAR_SIZE = 10
12
13local token = Replica.Token("ClientState")
14
15local blockCatalog = {}
16local playerReplicas = {} :: {[Player]: any}
17local blocksFolder: Folder? = nil
18local readyConnections = {} :: {[Player]: RBXScriptConnection}
19
20local function sortBlocks()
21 table.sort(blockCatalog, function(a, b)
22 local na = tonumber(a.id)
23 local nb = tonumber(b.id)
24 if na and nb then
25 return na < nb
26 end
27 if na then
28 return true
29 end
30 if nb then
31 return false
32 end
33 return a.id < b.id
34 end)
35end
36
37local function rebuildBlockCatalog()
38 table.clear(blockCatalog)
39 if not blocksFolder then
40 return
41 end
42
43 for _, block in ipairs(blocksFolder:GetChildren()) do
44 local id = block:GetAttribute("n")
45 if id ~= nil then
46 local displayName = block:GetAttribute("name") or block:GetAttribute("displayName") or block:GetAttribute("dn") or block.Name
47 table.insert(blockCatalog, {
48 id = tostring(id),
49 name = displayName,
50 })
51 end
52 end
53 sortBlocks()
54end
55
56local function makeBaseState()
57 local inventory = {}
58 local hotbar = {}
59
60 for _, entry in ipairs(blockCatalog) do
61 inventory[entry.id] = {
62 name = entry.name,
63 count = 999999,
64 }
65 if #hotbar < HOTBAR_SIZE then
66 table.insert(hotbar, entry.id)
67 end
68 end
69
70 return {
71 inventory = inventory,
72 hotbar = hotbar,
73 selectedSlot = #hotbar > 0 and 1 or 0,
74 }
75end
76
77local function sanitizeSelection(hotbar, selectedSlot)
78 if type(selectedSlot) ~= "number" then
79 return (#hotbar > 0) and 1 or 0
80 end
81 if selectedSlot < 1 or selectedSlot > HOTBAR_SIZE then
82 return (#hotbar > 0) and 1 or 0
83 end
84 return selectedSlot
85end
86
87local function refreshReplica(replica)
88 local state = makeBaseState()
89 replica:Set({"inventory"}, state.inventory)
90 replica:Set({"hotbar"}, state.hotbar)
91 replica:Set({"selectedSlot"}, sanitizeSelection(state.hotbar, replica.Data.selectedSlot))
92end
93
94function ClientStateService:SetBlocksFolder(folder: Folder?)
95 blocksFolder = folder
96 rebuildBlockCatalog()
97 for _, replica in pairs(playerReplicas) do
98 refreshReplica(replica)
99 end
100end
101
102function ClientStateService:GetReplica(player: Player)
103 return playerReplicas[player]
104end
105
106function ClientStateService:GetSelectedBlockId(player: Player)
107 local replica = playerReplicas[player]
108 if not replica then
109 return nil
110 end
111 local data = replica.Data
112 local hotbar = data.hotbar or {}
113 local selectedSlot = sanitizeSelection(hotbar, data.selectedSlot)
114 return hotbar[selectedSlot]
115end
116
117function ClientStateService:HasInInventory(player: Player, blockId: any): boolean
118 local replica = playerReplicas[player]
119 if not replica or not blockId then
120 return false
121 end
122 local inv = replica.Data.inventory
123 return inv and inv[tostring(blockId)] ~= nil or false
124end
125
126local function handleReplicaEvents(player: Player, replica)
127 replica.OnServerEvent:Connect(function(plr, action, payload)
128 if plr ~= player then
129 return
130 end
131
132 if action == "SelectHotbarSlot" then
133 local slot = tonumber(payload)
134 local hotbar = replica.Data.hotbar
135 if not hotbar then
136 return
137 end
138 if slot and slot >= 1 and slot <= HOTBAR_SIZE then
139 replica:Set({"selectedSlot"}, slot)
140 end
141 end
142 end)
143end
144
145local function onPlayerAdded(player: Player)
146 local replica = Replica.New({
147 Token = token,
148 Tags = {
149 UserId = player.UserId,
150 Player = player,
151 },
152 Data = makeBaseState(),
153 })
154
155 if Replica.ReadyPlayers[player] then
156 replica:Subscribe(player)
157 else
158 readyConnections[player] = Replica.NewReadyPlayer:Connect(function(newPlayer)
159 if newPlayer ~= player then
160 return
161 end
162 if readyConnections[player] then
163 readyConnections[player]:Disconnect()
164 readyConnections[player] = nil
165 end
166 replica:Subscribe(player)
167 end)
168 end
169
170 handleReplicaEvents(player, replica)
171 playerReplicas[player] = replica
172end
173
174local function onPlayerRemoving(player: Player)
175 local replica = playerReplicas[player]
176 if replica then
177 replica:Destroy()
178 playerReplicas[player] = nil
179 end
180 if readyConnections[player] then
181 readyConnections[player]:Disconnect()
182 readyConnections[player] = nil
183 end
184end
185
186function ClientStateService:Init()
187 rebuildBlockCatalog()
188
189 for _, player in ipairs(Players:GetPlayers()) do
190 onPlayerAdded(player)
191 end
192 Players.PlayerAdded:Connect(onPlayerAdded)
193 Players.PlayerRemoving:Connect(onPlayerRemoving)
194end
195
196return ClientStateService