chore: refractor
This commit is contained in:
453
StarterGui/Hotbar/LocalScript.client.lua
Normal file
453
StarterGui/Hotbar/LocalScript.client.lua
Normal file
@@ -0,0 +1,453 @@
|
||||
--!native
|
||||
--!optimize 2
|
||||
|
||||
if not game:IsLoaded() then
|
||||
game.Loaded:Wait()
|
||||
end
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local UIS = game:GetService("UserInputService")
|
||||
local TextChatService = game:GetService("TextChatService")
|
||||
|
||||
local objects = ReplicatedStorage:WaitForChild("Objects", 9e9)
|
||||
objects:WaitForChild("MLLoaded", 9e9)
|
||||
objects:WaitForChild("CSMLLoaded", 9e9)
|
||||
|
||||
local Roact = require(ReplicatedStorage.Packages.roact)
|
||||
local PM = require(ReplicatedStorage.Shared.PlacementManager)
|
||||
local BlockManager = require(ReplicatedStorage.Shared.ChunkManager.BlockManager)
|
||||
local PlacementState = require(ReplicatedStorage.Shared.PlacementState)
|
||||
local Util = require(ReplicatedStorage.Shared.Util)
|
||||
|
||||
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
||||
|
||||
local HOTBAR_SIZE = 10
|
||||
|
||||
local keyToSlot = {
|
||||
[Enum.KeyCode.One] = 1,
|
||||
[Enum.KeyCode.Two] = 2,
|
||||
[Enum.KeyCode.Three] = 3,
|
||||
[Enum.KeyCode.Four] = 4,
|
||||
[Enum.KeyCode.Five] = 5,
|
||||
[Enum.KeyCode.Six] = 6,
|
||||
[Enum.KeyCode.Seven] = 7,
|
||||
[Enum.KeyCode.Eight] = 8,
|
||||
[Enum.KeyCode.Nine] = 9,
|
||||
[Enum.KeyCode.Zero] = 10,
|
||||
}
|
||||
|
||||
local colors = {
|
||||
base = Color3.fromRGB(30, 30, 46),
|
||||
slot = Color3.fromRGB(17, 17, 27),
|
||||
stroke = Color3.fromRGB(88, 91, 112),
|
||||
selectedStroke = Color3.fromRGB(137, 180, 250),
|
||||
text = Color3.fromRGB(205, 214, 244),
|
||||
subtext = Color3.fromRGB(166, 173, 200),
|
||||
}
|
||||
|
||||
local function isTextInputFocused(): boolean
|
||||
if UIS:GetFocusedTextBox() then
|
||||
return true
|
||||
end
|
||||
local config = TextChatService:FindFirstChildOfClass("ChatInputBarConfiguration")
|
||||
return config ~= nil and config.IsFocused
|
||||
end
|
||||
|
||||
local function buildHotbarIds(): {string}
|
||||
local ids = {}
|
||||
local names = {}
|
||||
for _, block in ipairs(blocksFolder:GetChildren()) do
|
||||
local id = block:GetAttribute("n")
|
||||
if id ~= nil then
|
||||
local n = tonumber(id)
|
||||
if n and n > 0 then
|
||||
local idStr = tostring(n)
|
||||
table.insert(ids, idStr)
|
||||
names[idStr] = block:GetAttribute("displayName") or block:GetAttribute("dn") or block.Name
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(ids, function(a, b)
|
||||
local na = tonumber(a)
|
||||
local nb = tonumber(b)
|
||||
if na and nb then
|
||||
return na < nb
|
||||
end
|
||||
return a < b
|
||||
end)
|
||||
local slots = table.create(HOTBAR_SIZE)
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
slots[i] = ids[i] or ""
|
||||
end
|
||||
return slots, names
|
||||
end
|
||||
|
||||
local function ensurePreviewRig(part: Instance)
|
||||
for _, descendant in ipairs(part:GetDescendants()) do
|
||||
if descendant:IsA("BasePart") then
|
||||
descendant.Anchored = true
|
||||
descendant.CanCollide = false
|
||||
end
|
||||
end
|
||||
if part:IsA("BasePart") then
|
||||
part.Anchored = true
|
||||
part.CanCollide = false
|
||||
end
|
||||
end
|
||||
|
||||
local function updateViewport(viewport: ViewportFrame, blockId: string)
|
||||
viewport:ClearAllChildren()
|
||||
if blockId == "" then
|
||||
return
|
||||
end
|
||||
|
||||
local camera = Instance.new("Camera")
|
||||
camera.Parent = viewport
|
||||
viewport.CurrentCamera = camera
|
||||
|
||||
local world = Instance.new("WorldModel")
|
||||
world.Parent = viewport
|
||||
|
||||
local resolvedId = tonumber(blockId) or blockId
|
||||
local preview = BlockManager:GetBlock(resolvedId)
|
||||
preview.Parent = world
|
||||
ensurePreviewRig(preview)
|
||||
|
||||
local cf, size
|
||||
if preview:IsA("BasePart") then
|
||||
cf = preview.CFrame
|
||||
size = preview.Size
|
||||
else
|
||||
cf, size = preview:GetBoundingBox()
|
||||
end
|
||||
|
||||
local maxSize = math.max(size.X, size.Y, size.Z)
|
||||
local distance = maxSize * 1.8
|
||||
local target = cf.Position
|
||||
camera.CFrame = CFrame.new(target + Vector3.new(distance, distance, distance), target)
|
||||
preview:PivotTo(CFrame.new())
|
||||
end
|
||||
|
||||
local Hotbar = Roact.Component:extend("Hotbar")
|
||||
|
||||
function Hotbar:init()
|
||||
self.state = {
|
||||
slots = nil,
|
||||
names = nil,
|
||||
selected = 1,
|
||||
}
|
||||
local slots, names = buildHotbarIds()
|
||||
self.state.slots = slots
|
||||
self.state.names = names
|
||||
local initialId = slots and slots[1] or ""
|
||||
if initialId and initialId ~= "" then
|
||||
local initialName = names and (names[initialId] or initialId) or initialId
|
||||
PlacementState:SetSelected(initialId, initialName)
|
||||
end
|
||||
|
||||
self._updateSlots = function()
|
||||
local nextSlots, nextNames = buildHotbarIds()
|
||||
self:setState({
|
||||
slots = nextSlots,
|
||||
names = nextNames,
|
||||
})
|
||||
end
|
||||
|
||||
self._setSelected = function(slot: number)
|
||||
if slot < 1 or slot > HOTBAR_SIZE then
|
||||
return
|
||||
end
|
||||
self:setState({
|
||||
selected = slot,
|
||||
})
|
||||
local id = self.state.slots and self.state.slots[slot] or ""
|
||||
local name = ""
|
||||
if id ~= "" and self.state.names then
|
||||
name = self.state.names[id] or id
|
||||
end
|
||||
Util.StudioLog("[PLACE][CLIENT][SELECT]", "slot", slot, "id", id, "name", name)
|
||||
PlacementState:SetSelected(id, name)
|
||||
end
|
||||
|
||||
self._handleInput = function(input: InputObject, gameProcessedEvent: boolean)
|
||||
if isTextInputFocused() then
|
||||
return
|
||||
end
|
||||
|
||||
local slot = keyToSlot[input.KeyCode]
|
||||
if slot then
|
||||
if gameProcessedEvent then
|
||||
return
|
||||
end
|
||||
self._setSelected(slot)
|
||||
return
|
||||
end
|
||||
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
Util.StudioLog("[INPUT][CLIENT]", "MouseButton1", "processed", gameProcessedEvent)
|
||||
-- Allow click even if gameProcessedEvent (UI can set this), but only if we're actually pointing at a block
|
||||
if not PM:GetBlockAtMouse() then
|
||||
return
|
||||
end
|
||||
local mouseBlock = PM:GetBlockAtMouse()
|
||||
if not mouseBlock then
|
||||
return
|
||||
end
|
||||
PM:BreakBlock(
|
||||
mouseBlock.chunk.X,
|
||||
mouseBlock.chunk.Y,
|
||||
mouseBlock.chunk.Z,
|
||||
mouseBlock.block.X,
|
||||
mouseBlock.block.Y,
|
||||
mouseBlock.block.Z
|
||||
)
|
||||
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
|
||||
Util.StudioLog("[INPUT][CLIENT]", "MouseButton2", "processed", gameProcessedEvent)
|
||||
-- Allow click even if gameProcessedEvent (UI can set this), but only if we're actually pointing at a block
|
||||
local mouseBlock = PM:DebugGetPlacementOrWarn()
|
||||
if not mouseBlock then
|
||||
return
|
||||
end
|
||||
local id = PlacementState:GetSelected()
|
||||
if not id or id == "" then
|
||||
Util.StudioWarn("[PLACE][CLIENT][REJECT]", "no selected id")
|
||||
return
|
||||
end
|
||||
Util.StudioLog(
|
||||
"[PLACE][CLIENT][SEND][CLICK]",
|
||||
"chunk",
|
||||
mouseBlock.chunk,
|
||||
"block",
|
||||
mouseBlock.block,
|
||||
"id",
|
||||
id
|
||||
)
|
||||
PM:PlaceBlock(
|
||||
mouseBlock.chunk.X,
|
||||
mouseBlock.chunk.Y,
|
||||
mouseBlock.chunk.Z,
|
||||
mouseBlock.block.X,
|
||||
mouseBlock.block.Y,
|
||||
mouseBlock.block.Z,
|
||||
id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
self._handleScroll = function(input: InputObject, gameProcessedEvent: boolean)
|
||||
if gameProcessedEvent or isTextInputFocused() then
|
||||
return
|
||||
end
|
||||
if input.UserInputType ~= Enum.UserInputType.MouseWheel then
|
||||
return
|
||||
end
|
||||
local direction = input.Position.Z
|
||||
if direction == 0 then
|
||||
return
|
||||
end
|
||||
local delta = direction > 0 and -1 or 1
|
||||
local nextSlot = math.clamp(self.state.selected + delta, 1, HOTBAR_SIZE)
|
||||
if nextSlot ~= self.state.selected then
|
||||
self._setSelected(nextSlot)
|
||||
end
|
||||
end
|
||||
|
||||
self._viewportRefs = {}
|
||||
self._viewportState = {}
|
||||
end
|
||||
|
||||
function Hotbar:didMount()
|
||||
self._connections = {
|
||||
blocksFolder.ChildAdded:Connect(self._updateSlots),
|
||||
blocksFolder.ChildRemoved:Connect(self._updateSlots),
|
||||
UIS.InputBegan:Connect(self._handleInput),
|
||||
UIS.InputChanged:Connect(self._handleScroll),
|
||||
}
|
||||
self:_refreshViewports()
|
||||
-- initialize selection broadcast
|
||||
local id = self.state.slots and self.state.slots[self.state.selected] or ""
|
||||
local name = ""
|
||||
if id ~= "" and self.state.names then
|
||||
name = self.state.names[id] or id
|
||||
end
|
||||
PlacementState:SetSelected(id, name)
|
||||
end
|
||||
|
||||
function Hotbar:willUnmount()
|
||||
for _, conn in ipairs(self._connections or {}) do
|
||||
conn:Disconnect()
|
||||
end
|
||||
self._connections = nil
|
||||
end
|
||||
|
||||
function Hotbar:didUpdate(prevProps, prevState)
|
||||
if prevState.slots ~= self.state.slots then
|
||||
self:_refreshViewports()
|
||||
end
|
||||
end
|
||||
|
||||
function Hotbar:_refreshViewports()
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local viewport = self._viewportRefs[i]
|
||||
if viewport then
|
||||
local id = self.state.slots[i] or ""
|
||||
if self._viewportState[i] ~= id then
|
||||
self._viewportState[i] = id
|
||||
updateViewport(viewport, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Hotbar:render()
|
||||
local slotElements = {}
|
||||
local selectedId = self.state.slots[self.state.selected] or ""
|
||||
local selectedName = ""
|
||||
if selectedId ~= "" and self.state.names then
|
||||
selectedName = self.state.names[selectedId] or selectedId
|
||||
end
|
||||
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = self.state.slots[i] or ""
|
||||
local isSelected = i == self.state.selected
|
||||
|
||||
slotElements[`Slot{i-1}`] = Roact.createElement("TextButton", {
|
||||
Size = UDim2.fromOffset(50, 50),
|
||||
BackgroundColor3 = colors.slot,
|
||||
BorderSizePixel = 0,
|
||||
AutoButtonColor = false,
|
||||
ClipsDescendants = true,
|
||||
Text = "",
|
||||
LayoutOrder = i,
|
||||
[Roact.Event.Activated] = function()
|
||||
self._setSelected(i)
|
||||
end,
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 13),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = isSelected and colors.selectedStroke or colors.stroke,
|
||||
Thickness = isSelected and 2 or 1,
|
||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border
|
||||
}),
|
||||
Preview = Roact.createElement("ViewportFrame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BorderSizePixel = 0,
|
||||
[Roact.Ref] = function(r)
|
||||
self._viewportRefs[i] = r
|
||||
end,
|
||||
}),
|
||||
IndexLabel = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.fromOffset(4, 2),
|
||||
Size = UDim2.fromOffset(18, 14),
|
||||
Font = Enum.Font.Gotham,
|
||||
Text = i == 10 and "0" or tostring(i),
|
||||
TextColor3 = colors.subtext,
|
||||
TextSize = 12,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
}),
|
||||
IdLabel = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Position = UDim2.fromOffset(4, 26),
|
||||
Size = UDim2.new(1, -8, 0, 18),
|
||||
Font = Enum.Font.GothamBold,
|
||||
Text = id,
|
||||
TextColor3 = colors.text,
|
||||
TextSize = 15,
|
||||
TextWrapped = true,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Bottom,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local hotbarFrame = Roact.createElement("Frame", {
|
||||
AnchorPoint = Vector2.new(0.5, 1),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundColor3 = colors.base,
|
||||
BorderSizePixel = 0,
|
||||
Position = UDim2.new(0.5, 0, 1, -20),
|
||||
Size = UDim2.fromOffset(0, 58),
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 16),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = colors.selectedStroke,
|
||||
Thickness = 2,
|
||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border
|
||||
}),
|
||||
Padding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 5),
|
||||
PaddingRight = UDim.new(0, 5),
|
||||
PaddingTop = UDim.new(0, 5),
|
||||
PaddingBottom = UDim.new(0, 5),
|
||||
}),
|
||||
Slots = Roact.createElement("Frame", {
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0, 0, 1, 0),
|
||||
}, {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
Padding = UDim.new(0, 5),
|
||||
}),
|
||||
Slots = Roact.createFragment(slotElements),
|
||||
}),
|
||||
})
|
||||
local selectedNameFrame = Roact.createElement("Frame", {
|
||||
AnchorPoint = Vector2.new(0.5, 1),
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
BackgroundColor3 = colors.base,
|
||||
BorderSizePixel = 0,
|
||||
Position = UDim2.new(0.5, 0, 1, -80-10),
|
||||
Size = UDim2.fromOffset(0, 25),
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 8),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = colors.selectedStroke,
|
||||
Thickness = 2,
|
||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border
|
||||
}),
|
||||
Padding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 18),
|
||||
PaddingRight = UDim.new(0, 18),
|
||||
PaddingTop = UDim.new(0, 2),
|
||||
PaddingBottom = UDim.new(0, 2),
|
||||
}),
|
||||
Label = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Font = Enum.Font.JosefinSans,
|
||||
RichText = true,
|
||||
Text = selectedName ~= "" and selectedName or " ",
|
||||
TextColor3 = colors.text,
|
||||
TextSize = 19,
|
||||
TextWrapped = true,
|
||||
AutomaticSize = Enum.AutomaticSize.X,
|
||||
TextXAlignment = Enum.TextXAlignment.Center,
|
||||
TextYAlignment = Enum.TextYAlignment.Center
|
||||
}),
|
||||
})
|
||||
|
||||
return Roact.createFragment({
|
||||
Hotbar = hotbarFrame,
|
||||
SelectedName = selectedNameFrame,
|
||||
})
|
||||
end
|
||||
|
||||
local handle = Roact.mount(Roact.createElement(Hotbar), script.Parent, "RoactHotbar")
|
||||
|
||||
script.AncestryChanged:Connect(function(_, parent)
|
||||
if parent == nil then
|
||||
Roact.unmount(handle)
|
||||
end
|
||||
end)
|
||||
Reference in New Issue
Block a user