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

hotbar: wrap

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