Minecraft-like Roblox block game rblx.games/135624152691584
roblox roblox-game rojo

hotbar: wrap

+81 -51
+4 -2
ReplicatedStorage/Shared/ClientState.lua
··· 117 return 118 end 119 local hotbar = replicaForPlayer.Data.hotbar 120 - if not hotbar or not hotbar[slot] then 121 return 122 end 123 - replicaForPlayer:FireServer("SelectHotbarSlot", slot) 124 end 125 126 ClientState.Changed = changed.Event
··· 117 return 118 end 119 local hotbar = replicaForPlayer.Data.hotbar 120 + if not hotbar then 121 return 122 end 123 + if slot and slot >= 1 and slot <= HOTBAR_SIZE then 124 + replicaForPlayer:FireServer("SelectHotbarSlot", slot) 125 + end 126 end 127 128 ClientState.Changed = changed.Event
+53 -25
ReplicatedStorage/Shared/PlacementManager.lua
··· 79 if current:IsA("BasePart") then 80 return current 81 end 82 - current = current.Parent 83 end 84 return nil 85 end ··· 265 end 266 267 -- Gets the block and normalid of the block (and surface) the player is looking at 268 - function PlacementManager:Raycast() 269 if not Mouse then 270 Mouse = game:GetService("Players").LocalPlayer:GetMouse() 271 end 272 local chunkFolder = ensureChunkFolder() 273 if not chunkFolder then 274 - clearSelection("chunk folder missing") 275 script.RaycastResult.Value = nil 276 return 277 end ··· 285 local ray = Mouse.UnitRay 286 local result = workspace:Raycast(ray.Origin, ray.Direction * MAX_REACH, raycastParams) 287 if not result then 288 - clearSelection("raycast miss") 289 script.RaycastResult.Value = nil 290 debugPlacementLog("[PLACE][CLIENT][RAYCAST]", "miss") 291 return ··· 293 294 local objLookingAt = result.Instance 295 if not objLookingAt then 296 - clearSelection("raycast nil instance") 297 script.RaycastResult.Value = nil 298 debugPlacementWarn("[PLACE][CLIENT][RAYCAST]", "nil instance in result") 299 return ··· 308 "parent", 309 objLookingAt.Parent and objLookingAt.Parent:GetFullName() or "nil" 310 ) 311 - clearSelection("target not in chunk folder") 312 script.RaycastResult.Value = nil 313 return 314 end ··· 318 "chunk flagged ns", 319 hitChunkFolder:GetFullName() 320 ) 321 - clearSelection("target chunk marked ns") 322 script.RaycastResult.Value = nil 323 return 324 end ··· 327 local blockRoot = findBlockRoot(objLookingAt, chunkFolder) or objLookingAt 328 local chunkName, blockName = findChunkAndBlock(blockRoot) 329 if not chunkName or not blockName then 330 - clearSelection("failed to resolve chunk/block") 331 script.RaycastResult.Value = nil 332 return 333 end ··· 338 return Util.BlockPosStringToCoords(blockName) 339 end) 340 if not okChunk or not okBlock then 341 - clearSelection("failed to parse chunk/block names") 342 script.RaycastResult.Value = nil 343 return 344 end ··· 347 348 -- block is being optimistically broken, do not highlight it 349 if getPendingBreak(chunkKey, blockKey) then 350 - clearSelection("block pending break") 351 script.RaycastResult.Value = nil 352 return 353 end ··· 356 local chunk = ChunkManager:GetChunk(chunkCoords.X, chunkCoords.Y, chunkCoords.Z) 357 local blockData = chunk and chunk:GetBlockAt(blockCoords.X, blockCoords.Y, blockCoords.Z) 358 if not blockData or blockData == 0 or blockData.id == 0 then 359 - clearSelection("block missing/air") 360 script.RaycastResult.Value = nil 361 return 362 end 363 local blockInstance = resolveBlockInstance(chunkFolder, chunkName, blockName) or blockRoot 364 if not blockInstance then 365 - clearSelection("missing block instance") 366 script.RaycastResult.Value = nil 367 return 368 end 369 370 lastRaycastFailure = nil 371 - if lastSelectedChunkKey ~= chunkKey or lastSelectedBlockKey ~= blockKey then 372 - setSelection(blockInstance, PlacementManager.ChunkFolder) 373 - lastSelectedChunkKey = chunkKey 374 - lastSelectedBlockKey = blockKey 375 end 376 script.RaycastResult.Value = objLookingAt 377 lastNormalId = vectorToNormalId(result.Normal) ··· 400 -- FIRES REMOTE 401 function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string) 402 debugPlacementLog("[PLACE][CLIENT][PLACE_CALL]", "chunk", cx, cy, cz, "block", x, y, z, "blockId", blockId) 403 if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then 404 debugPlacementWarn("[PLACE][CLIENT][REJECT]", "chunk type", cx, cy, cz, x, y, z, blockId) 405 return ··· 551 chunk:RemoveBlock(x, y, z) 552 end 553 554 - function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector3} 555 pcall(function() 556 - PlacementManager:Raycast() 557 end) 558 local selectedPart = PlacementManager:RaycastGetResult() 559 --print(selectedPart and selectedPart:GetFullName() or nil) 560 if selectedPart == nil then 561 - clearSelection() 562 script.RaycastResult.Value = nil 563 debugPlacementLog("[PLACE][CLIENT][TARGET]", "no selectedPart after raycast", lastRaycastFailure) 564 return nil ··· 607 608 end 609 610 - function PlacementManager:GetTargetAtMouse(): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId} 611 - local hit = PlacementManager:GetBlockAtMouse() 612 if not hit then 613 return nil 614 end ··· 621 } 622 end 623 624 - function PlacementManager:GetPlacementAtMouse(): nil | {chunk:Vector3, block: Vector3} 625 - local hit = PlacementManager:GetTargetAtMouse() 626 if not hit then 627 return nil 628 end ··· 647 } 648 end 649 650 - function PlacementManager:DebugGetPlacementOrWarn() 651 - local placement = PlacementManager:GetPlacementAtMouse() 652 if not placement then 653 debugPlacementWarn("[PLACE][CLIENT][REJECT]", "no placement target under mouse", lastRaycastFailure) 654 end
··· 79 if current:IsA("BasePart") then 80 return current 81 end 82 + current = current.Parent 83 end 84 return nil 85 end ··· 265 end 266 267 -- Gets the block and normalid of the block (and surface) the player is looking at 268 + function PlacementManager:Raycast(skipSelection: boolean?) 269 if not Mouse then 270 Mouse = game:GetService("Players").LocalPlayer:GetMouse() 271 end 272 local chunkFolder = ensureChunkFolder() 273 if not chunkFolder then 274 + if not skipSelection then 275 + clearSelection("chunk folder missing") 276 + end 277 script.RaycastResult.Value = nil 278 return 279 end ··· 287 local ray = Mouse.UnitRay 288 local result = workspace:Raycast(ray.Origin, ray.Direction * MAX_REACH, raycastParams) 289 if not result then 290 + if not skipSelection then 291 + clearSelection("raycast miss") 292 + end 293 script.RaycastResult.Value = nil 294 debugPlacementLog("[PLACE][CLIENT][RAYCAST]", "miss") 295 return ··· 297 298 local objLookingAt = result.Instance 299 if not objLookingAt then 300 + if not skipSelection then 301 + clearSelection("raycast nil instance") 302 + end 303 script.RaycastResult.Value = nil 304 debugPlacementWarn("[PLACE][CLIENT][RAYCAST]", "nil instance in result") 305 return ··· 314 "parent", 315 objLookingAt.Parent and objLookingAt.Parent:GetFullName() or "nil" 316 ) 317 + if not skipSelection then 318 + clearSelection("target not in chunk folder") 319 + end 320 script.RaycastResult.Value = nil 321 return 322 end ··· 326 "chunk flagged ns", 327 hitChunkFolder:GetFullName() 328 ) 329 + if not skipSelection then 330 + clearSelection("target chunk marked ns") 331 + end 332 script.RaycastResult.Value = nil 333 return 334 end ··· 337 local blockRoot = findBlockRoot(objLookingAt, chunkFolder) or objLookingAt 338 local chunkName, blockName = findChunkAndBlock(blockRoot) 339 if not chunkName or not blockName then 340 + if not skipSelection then 341 + clearSelection("failed to resolve chunk/block") 342 + end 343 script.RaycastResult.Value = nil 344 return 345 end ··· 350 return Util.BlockPosStringToCoords(blockName) 351 end) 352 if not okChunk or not okBlock then 353 + if not skipSelection then 354 + clearSelection("failed to parse chunk/block names") 355 + end 356 script.RaycastResult.Value = nil 357 return 358 end ··· 361 362 -- block is being optimistically broken, do not highlight it 363 if getPendingBreak(chunkKey, blockKey) then 364 + if not skipSelection then 365 + clearSelection("block pending break") 366 + end 367 script.RaycastResult.Value = nil 368 return 369 end ··· 372 local chunk = ChunkManager:GetChunk(chunkCoords.X, chunkCoords.Y, chunkCoords.Z) 373 local blockData = chunk and chunk:GetBlockAt(blockCoords.X, blockCoords.Y, blockCoords.Z) 374 if not blockData or blockData == 0 or blockData.id == 0 then 375 + if not skipSelection then 376 + clearSelection("block missing/air") 377 + end 378 script.RaycastResult.Value = nil 379 return 380 end 381 local blockInstance = resolveBlockInstance(chunkFolder, chunkName, blockName) or blockRoot 382 if not blockInstance then 383 + if not skipSelection then 384 + clearSelection("missing block instance") 385 + end 386 script.RaycastResult.Value = nil 387 return 388 end 389 390 lastRaycastFailure = nil 391 + if not skipSelection then 392 + if lastSelectedChunkKey ~= chunkKey or lastSelectedBlockKey ~= blockKey then 393 + setSelection(blockInstance, PlacementManager.ChunkFolder) 394 + lastSelectedChunkKey = chunkKey 395 + lastSelectedBlockKey = blockKey 396 + end 397 end 398 script.RaycastResult.Value = objLookingAt 399 lastNormalId = vectorToNormalId(result.Normal) ··· 422 -- FIRES REMOTE 423 function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string) 424 debugPlacementLog("[PLACE][CLIENT][PLACE_CALL]", "chunk", cx, cy, cz, "block", x, y, z, "blockId", blockId) 425 + if blockId == "hand" then 426 + debugPlacementWarn("[PLACE][CLIENT][REJECT]", "hand cannot place") 427 + return 428 + end 429 if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then 430 debugPlacementWarn("[PLACE][CLIENT][REJECT]", "chunk type", cx, cy, cz, x, y, z, blockId) 431 return ··· 577 chunk:RemoveBlock(x, y, z) 578 end 579 580 + function PlacementManager:GetBlockAtMouse(skipSelection: boolean?): nil | {chunk:Vector3, block: Vector3} 581 pcall(function() 582 + PlacementManager:Raycast(skipSelection) 583 end) 584 local selectedPart = PlacementManager:RaycastGetResult() 585 --print(selectedPart and selectedPart:GetFullName() or nil) 586 if selectedPart == nil then 587 + if not skipSelection then 588 + clearSelection() 589 + end 590 script.RaycastResult.Value = nil 591 debugPlacementLog("[PLACE][CLIENT][TARGET]", "no selectedPart after raycast", lastRaycastFailure) 592 return nil ··· 635 636 end 637 638 + function PlacementManager:GetTargetAtMouse(skipSelection: boolean?): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId} 639 + local hit = PlacementManager:GetBlockAtMouse(skipSelection) 640 if not hit then 641 return nil 642 end ··· 649 } 650 end 651 652 + function PlacementManager:GetPlacementAtMouse(skipSelection: boolean?): nil | {chunk:Vector3, block: Vector3} 653 + local hit = PlacementManager:GetTargetAtMouse(skipSelection) 654 if not hit then 655 return nil 656 end ··· 675 } 676 end 677 678 + function PlacementManager:DebugGetPlacementOrWarn(skipSelection: boolean?) 679 + local placement = PlacementManager:GetPlacementAtMouse(skipSelection) 680 if not placement then 681 debugPlacementWarn("[PLACE][CLIENT][REJECT]", "no placement target under mouse", lastRaycastFailure) 682 end
+1 -4
ServerScriptService/Actor/ClientState.lua
··· 81 if selectedSlot < 1 or selectedSlot > HOTBAR_SIZE then 82 return (#hotbar > 0) and 1 or 0 83 end 84 - if not hotbar[selectedSlot] then 85 - return (#hotbar > 0) and 1 or 0 86 - end 87 return selectedSlot 88 end 89 ··· 138 if not hotbar then 139 return 140 end 141 - if slot and slot >= 1 and slot <= HOTBAR_SIZE and hotbar[slot] then 142 replica:Set({"selectedSlot"}, slot) 143 end 144 end
··· 81 if selectedSlot < 1 or selectedSlot > HOTBAR_SIZE then 82 return (#hotbar > 0) and 1 or 0 83 end 84 return selectedSlot 85 end 86 ··· 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
+23 -20
StarterGui/Hotbar/LocalScript.client.lua
··· 53 end 54 55 local function resolveSelectedSlot(slots, desired) 56 - if desired and desired >= 1 and desired <= HOTBAR_SIZE and slots[desired] ~= "" then 57 return desired 58 end 59 for i = 1, HOTBAR_SIZE do ··· 146 names = nextNames, 147 selected = nextSelected, 148 }) 149 - local id = nextSlots[nextSelected] or "" 150 local name = "" 151 - if id ~= "" then 152 - name = nextNames[id] or id 153 end 154 - PlacementState:SetSelected(id, name) 155 end 156 157 self._setSelected = function(slot: number) 158 if slot < 1 or slot > HOTBAR_SIZE then 159 return 160 end 161 - local info = ClientState:GetSlotInfo(slot) 162 - if not info then 163 - return 164 - end 165 ClientState:SetSelectedSlot(slot) 166 self:setState({ 167 selected = slot, 168 }) 169 - local id = tostring(info.id) 170 - local name = info.name or id 171 - Util.StudioLog("[PLACE][CLIENT][SELECT]", "slot", slot, "id", id, "name", name) 172 - PlacementState:SetSelected(id, name) 173 end 174 175 self._handleInput = function(input: InputObject, gameProcessedEvent: boolean) ··· 207 elseif input.UserInputType == Enum.UserInputType.MouseButton2 then 208 Util.StudioLog("[INPUT][CLIENT]", "MouseButton2", "processed", gameProcessedEvent) 209 -- Allow click even if gameProcessedEvent (UI can set this), but only if we're actually pointing at a block 210 - local mouseBlock = PM:DebugGetPlacementOrWarn() 211 if not mouseBlock then 212 return 213 end ··· 249 return 250 end 251 local delta = direction > 0 and -1 or 1 252 - local nextSlot = math.clamp(self.state.selected + delta, 1, HOTBAR_SIZE) 253 if nextSlot ~= self.state.selected then 254 self._setSelected(nextSlot) 255 end ··· 268 self._syncFromClientState() 269 self:_refreshViewports() 270 -- initialize selection broadcast 271 - local id = self.state.slots and self.state.slots[self.state.selected] or "" 272 local name = "" 273 - if id ~= "" and self.state.names then 274 - name = self.state.names[id] or id 275 end 276 - PlacementState:SetSelected(id, name) 277 end 278 279 function Hotbar:willUnmount() ··· 345 }), 346 IndexLabel = Roact.createElement("TextLabel", { 347 BackgroundTransparency = 1, 348 - Position = UDim2.fromOffset(4, 2), 349 Size = UDim2.fromOffset(18, 14), 350 Font = Enum.Font.Gotham, 351 Text = i == 10 and "0" or tostring(i), ··· 412 BorderSizePixel = 0, 413 Position = UDim2.new(0.5, 0, 1, -80-10), 414 Size = UDim2.fromOffset(0, 25), 415 }, { 416 Corner = Roact.createElement("UICorner", { 417 CornerRadius = UDim.new(0, 8),
··· 53 end 54 55 local function resolveSelectedSlot(slots, desired) 56 + if desired and desired >= 1 and desired <= HOTBAR_SIZE then 57 return desired 58 end 59 for i = 1, HOTBAR_SIZE do ··· 146 names = nextNames, 147 selected = nextSelected, 148 }) 149 + local rawId = nextSlots[nextSelected] or "" 150 + local effectiveId = rawId ~= "" and rawId or "hand" 151 local name = "" 152 + if rawId ~= "" then 153 + name = nextNames[rawId] or rawId 154 end 155 + PlacementState:SetSelected(effectiveId, name) 156 end 157 158 self._setSelected = function(slot: number) 159 if slot < 1 or slot > HOTBAR_SIZE then 160 return 161 end 162 ClientState:SetSelectedSlot(slot) 163 self:setState({ 164 selected = slot, 165 }) 166 + local rawId = self.state.slots[slot] or "" 167 + local effectiveId = rawId ~= "" and rawId or "hand" 168 + local name = "" 169 + if rawId ~= "" then 170 + name = self.state.names[rawId] or rawId 171 + end 172 + Util.StudioLog("[PLACE][CLIENT][SELECT]", "slot", slot, "id", effectiveId, "name", name) 173 + PlacementState:SetSelected(effectiveId, name) 174 end 175 176 self._handleInput = function(input: InputObject, gameProcessedEvent: boolean) ··· 208 elseif input.UserInputType == Enum.UserInputType.MouseButton2 then 209 Util.StudioLog("[INPUT][CLIENT]", "MouseButton2", "processed", gameProcessedEvent) 210 -- Allow click even if gameProcessedEvent (UI can set this), but only if we're actually pointing at a block 211 + local mouseBlock = PM:DebugGetPlacementOrWarn(true) -- skip selection outline on right click 212 if not mouseBlock then 213 return 214 end ··· 250 return 251 end 252 local delta = direction > 0 and -1 or 1 253 + local nextSlot = ((self.state.selected - 1 + delta) % HOTBAR_SIZE) + 1 254 if nextSlot ~= self.state.selected then 255 self._setSelected(nextSlot) 256 end ··· 269 self._syncFromClientState() 270 self:_refreshViewports() 271 -- initialize selection broadcast 272 + local rawId = self.state.slots and self.state.slots[self.state.selected] or "" 273 + local effectiveId = rawId ~= "" and rawId or "hand" 274 local name = "" 275 + if rawId ~= "" and self.state.names then 276 + name = self.state.names[rawId] or rawId 277 end 278 + PlacementState:SetSelected(effectiveId, name) 279 end 280 281 function Hotbar:willUnmount() ··· 347 }), 348 IndexLabel = Roact.createElement("TextLabel", { 349 BackgroundTransparency = 1, 350 + Position = UDim2.fromOffset(8, 4), 351 Size = UDim2.fromOffset(18, 14), 352 Font = Enum.Font.Gotham, 353 Text = i == 10 and "0" or tostring(i), ··· 414 BorderSizePixel = 0, 415 Position = UDim2.new(0.5, 0, 1, -80-10), 416 Size = UDim2.fromOffset(0, 25), 417 + Visible = selectedName ~= "", 418 }, { 419 Corner = Roact.createElement("UICorner", { 420 CornerRadius = UDim.new(0, 8),