--[[ Keyboard Layout v3 - 10 layout slots, named - Tap a slot name -> load + lock (no dragging) - Tap ✎ -> load + open edit mode (drag + save) - Tap ✕ (x2) -> delete slot - + CREATE NEW / BUILD WITHOUT SAVING --]] task.wait(7) local Players = game:GetService("Players") local UIS = game:GetService("UserInputService") local RunService = game:GetService("RunService") local VIM = game:GetService("VirtualInputManager") local PPromptSvc = game:GetService("ProximityPromptService") local HttpSvc = game:GetService("HttpService") local player = Players.LocalPlayer local playerGui = player:WaitForChild("PlayerGui") local isMobile = UIS.TouchEnabled and not UIS.KeyboardEnabled pcall(function() UIS.MouseEnabled = true end) pcall(function() UIS.KeyboardEnabled = true end) ---------------------------------------------------------------- -- CONFIG ---------------------------------------------------------------- local CONFIG = { KeySize = isMobile and 72 or 48, KeySpacing = isMobile and 8 or 6, CornerR = isMobile and 12 or 8, BaseColor = Color3.fromRGB(30, 30, 36), PressedColor = Color3.fromRGB(90, 160, 255), ActiveColor = Color3.fromRGB(80, 220, 130), TextColor = Color3.fromRGB(235, 235, 245), KeyFont = Enum.Font.GothamBold, TextSize = isMobile and 26 or 18, PanelColor = Color3.fromRGB(20, 20, 26), PanelAccent = Color3.fromRGB(70, 130, 200), PanelGreen = Color3.fromRGB(60, 180, 100), PanelRed = Color3.fromRGB(180, 60, 60), } local WASD_POS = isMobile and UDim2.fromScale(0.20, 0.72) or UDim2.fromScale(0.5, 0.83) local SPACE_POS = isMobile and UDim2.fromScale(0.80, 0.68) or UDim2.fromScale(0.5, 0.92) local SHIFT_POS = isMobile and UDim2.fromScale(0.80, 0.85) or UDim2.fromScale(0.62, 0.92) ---------------------------------------------------------------- -- ADDABLE KEYS ---------------------------------------------------------------- local ADDABLE_KEYS = { {label="E",code=Enum.KeyCode.E}, {label="Q",code=Enum.KeyCode.Q}, {label="R",code=Enum.KeyCode.R}, {label="F",code=Enum.KeyCode.F}, {label="G",code=Enum.KeyCode.G}, {label="C",code=Enum.KeyCode.C}, {label="V",code=Enum.KeyCode.V}, {label="X",code=Enum.KeyCode.X}, {label="Z",code=Enum.KeyCode.Z}, {label="T",code=Enum.KeyCode.T}, {label="Y",code=Enum.KeyCode.Y}, {label="H",code=Enum.KeyCode.H}, {label="B",code=Enum.KeyCode.B}, {label="N",code=Enum.KeyCode.N}, {label="M",code=Enum.KeyCode.M}, {label="J",code=Enum.KeyCode.J}, {label="K",code=Enum.KeyCode.K}, {label="L",code=Enum.KeyCode.L}, {label="P",code=Enum.KeyCode.P}, {label="O",code=Enum.KeyCode.O}, {label="I",code=Enum.KeyCode.I}, {label="U",code=Enum.KeyCode.U}, {label="1",code=Enum.KeyCode.One}, {label="2",code=Enum.KeyCode.Two}, {label="3",code=Enum.KeyCode.Three},{label="4",code=Enum.KeyCode.Four}, {label="5",code=Enum.KeyCode.Five}, {label="LMB",code=Enum.UserInputType.MouseButton1,isMouse=true}, {label="RMB",code=Enum.UserInputType.MouseButton2,isMouse=true}, {label="L-Ctrl",code=Enum.KeyCode.LeftControl}, {label="R-Ctrl",code=Enum.KeyCode.RightControl}, {label="Alt",code=Enum.KeyCode.LeftAlt}, {label="Tab",code=Enum.KeyCode.Tab}, {label="Esc",code=Enum.KeyCode.Escape}, {label="Enter",code=Enum.KeyCode.Return}, } ---------------------------------------------------------------- -- PERSISTENCE ---------------------------------------------------------------- local FILE_PATH = "kb_layouts_v2.json" local layoutsData = {} local function loadLayouts() local ok, content = pcall(function() if isfile and isfile(FILE_PATH) then return readfile(FILE_PATH) end end) if ok and content then local ok2, data = pcall(function() return HttpSvc:JSONDecode(content) end) if ok2 and type(data) == "table" then layoutsData = data end end for i = 1, 10 do if layoutsData[i] == nil then layoutsData[i] = false end end end local function saveLayouts() pcall(function() if writefile then writefile(FILE_PATH, HttpSvc:JSONEncode(layoutsData)) end end) end loadLayouts() ---------------------------------------------------------------- -- DISABLE DEFAULT MOBILE CONTROLS ---------------------------------------------------------------- if isMobile then task.spawn(function() local ps = player:WaitForChild("PlayerScripts") local mod = require(ps:WaitForChild("PlayerModule")) mod:GetControls():Disable() end) task.spawn(function() local touchGui = playerGui:WaitForChild("TouchGui", 5) if touchGui then touchGui.Enabled = false end end) end ---------------------------------------------------------------- -- ROOT GUI ---------------------------------------------------------------- local gui = Instance.new("ScreenGui") gui.Name = "KeyboardLayout" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true gui.DisplayOrder = 100 gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling gui.Parent = playerGui local K, S = CONFIG.KeySize, CONFIG.KeySpacing ---------------------------------------------------------------- -- STATE ---------------------------------------------------------------- local held = { W=false, A=false, S=false, D=false, Space=false } local shiftLockOn = false local setupMode = true local dragState = nil local pressState = {} local containers = {} local activeLayoutIndex = nil local containersById = {} ---------------------------------------------------------------- -- HELPERS ---------------------------------------------------------------- local function makeContainer(pos, w, h) local c = Instance.new("Frame") c.BackgroundTransparency = 1 c.AnchorPoint = Vector2.new(0.5, 0.5) c.Position = pos c.Size = UDim2.fromOffset(w, h) c.Parent = gui containers[c] = true return c end local function makeKey(parent, text, posX, posY, width) local key = Instance.new("TextButton") key.Text = text key.TextColor3 = CONFIG.TextColor key.Font = CONFIG.KeyFont key.TextSize = CONFIG.TextSize key.AutoButtonColor = false key.BackgroundColor3 = CONFIG.BaseColor key.BorderSizePixel = 0 key.ZIndex = 2 key.Selectable = false key.Active = true key.Size = UDim2.fromOffset(width or K, K) key.Position = UDim2.fromOffset(posX, posY) local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, CONFIG.CornerR) corner.Parent = key local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(60, 60, 70) stroke.Thickness = 1 stroke.Parent = key key.Parent = parent return key end local function udim2ToTable(u) return {u.X.Scale, u.X.Offset, u.Y.Scale, u.Y.Offset} end local function tableToUdim2(t) if type(t) ~= "table" or #t < 4 then return UDim2.fromScale(0.5, 0.5) end return UDim2.new(t[1], t[2], t[3], t[4]) end ---------------------------------------------------------------- -- PROMPT HANDLING ---------------------------------------------------------------- local activePrompts = {} local heldPrompts = {} local function forceCustomStyle(prompt) pcall(function() if prompt.Style ~= Enum.ProximityPromptStyle.Custom then prompt.Style = Enum.ProximityPromptStyle.Custom end end) end local function makePromptBillboard(prompt) local target = prompt.Parent if not target or not target:IsA("BasePart") then return nil end local bb = Instance.new("BillboardGui") bb.Name = "KBLayoutPromptHint" bb.Size = UDim2.fromOffset(110, 36) bb.StudsOffset = Vector3.new(0, 2.5, 0) bb.AlwaysOnTop = true bb.MaxDistance = prompt.MaxActivationDistance or 10 bb.Parent = target local frame = Instance.new("Frame") frame.Size = UDim2.fromScale(1, 1) frame.BackgroundColor3 = Color3.fromRGB(20, 20, 26) frame.BackgroundTransparency = 0.15 frame.BorderSizePixel = 0 frame.Parent = bb local fc = Instance.new("UICorner") fc.CornerRadius = UDim.new(0, 8) fc.Parent = frame local label = Instance.new("TextLabel") label.Size = UDim2.fromScale(1, 1) label.BackgroundTransparency = 1 label.TextColor3 = Color3.fromRGB(240, 240, 250) label.Font = Enum.Font.GothamBold label.TextSize = 16 label.Text = string.format("[%s] %s", prompt.KeyboardKeyCode.Name, prompt.ActionText ~= "" and prompt.ActionText or "Interact") label.Parent = frame return bb end for _, d in ipairs(game:GetDescendants()) do if d:IsA("ProximityPrompt") then forceCustomStyle(d) end end game.DescendantAdded:Connect(function(d) if d:IsA("ProximityPrompt") then forceCustomStyle(d) end end) PPromptSvc.PromptShown:Connect(function(prompt) forceCustomStyle(prompt) local bb = makePromptBillboard(prompt) activePrompts[prompt] = {billboard = bb} end) PPromptSvc.PromptHidden:Connect(function(prompt) local data = activePrompts[prompt] if data and data.billboard then data.billboard:Destroy() end activePrompts[prompt] = nil heldPrompts[prompt] = nil end) ---------------------------------------------------------------- -- BUILD DEFAULT CLUSTERS ---------------------------------------------------------------- local wasdContainer = makeContainer(WASD_POS, K*3 + S*2, K*2 + S) local spaceContainer = makeContainer(SPACE_POS, K, K) local shiftContainer = makeContainer(SHIFT_POS, K, K) containersById.wasd = wasdContainer containersById.space = spaceContainer containersById.shift = shiftContainer local keyW = makeKey(wasdContainer, "W", 1*(K+S), 0) local keyA = makeKey(wasdContainer, "A", 0, 1*(K+S)) local keyS = makeKey(wasdContainer, "S", 1*(K+S), 1*(K+S)) local keyD = makeKey(wasdContainer, "D", 2*(K+S), 1*(K+S)) local spaceKey = makeKey(spaceContainer, "⤒", 0, 0, K) spaceKey.TextSize = isMobile and 40 or 24 local shiftKey = makeKey(shiftContainer, "🔒", 0, 0, K) shiftKey.TextSize = isMobile and 32 or 20 ---------------------------------------------------------------- -- ADDED KEYS ---------------------------------------------------------------- local addedKeys = {} local function findAddedEntry(button) for _, e in ipairs(addedKeys) do if e.button == button then return e end end return nil end local function addKey(label, code, isMouse, position) local c = makeContainer(position or UDim2.fromScale(0.5, 0.4), K, K) local btn = makeKey(c, label, 0, 0, K) if #label > 2 then btn.TextSize = math.max(10, CONFIG.TextSize - 8) end local isInteract = (code == Enum.KeyCode.E or code == Enum.KeyCode.F) and not isMouse local entry = { container = c, button = btn, code = code, isMouse = isMouse, isInteract = isInteract, } table.insert(addedKeys, entry) btn.InputBegan:Connect(function(input) if input.UserInputType ~= Enum.UserInputType.MouseButton1 and input.UserInputType ~= Enum.UserInputType.Touch then return end pressState[input] = { button = btn, id = "ADDED", wasDrag = false } btn.BackgroundColor3 = CONFIG.PressedColor if isInteract then for prompt in pairs(activePrompts) do if prompt.Parent and not heldPrompts[prompt] then heldPrompts[prompt] = true task.spawn(function() pcall(function() prompt:InputHoldBegin() end) end) end end elseif not isMouse then VIM:SendKeyEvent(true, code, false, game) end if setupMode and not dragState then dragState = { input = input, container = c, startMouse = input.Position, startPos = c.Position, moved = false, } end end) return entry end local function clearAddedKeys() for _, e in ipairs(addedKeys) do e.container:Destroy() end addedKeys = {} end ---------------------------------------------------------------- -- LAYOUT SNAPSHOT / APPLY ---------------------------------------------------------------- local function snapshotLayout() local snap = {containers = {}, addedKeys = {}} for id, c in pairs(containersById) do snap.containers[id] = udim2ToTable(c.Position) end for _, e in ipairs(addedKeys) do table.insert(snap.addedKeys, { label = e.button.Text, codeName = e.code.Name, isMouse = e.isMouse, pos = udim2ToTable(e.container.Position), }) end return snap end local function applyLayout(layout) if not layout then return end for id, pos in pairs(layout.containers or {}) do local c = containersById[id] if c then c.Position = tableToUdim2(pos) end end clearAddedKeys() for _, k in ipairs(layout.addedKeys or {}) do local code if k.isMouse then code = Enum.UserInputType[k.codeName] else code = Enum.KeyCode[k.codeName] end if code then addKey(k.label, code, k.isMouse, tableToUdim2(k.pos)) end end end local function resetToDefault() containersById.wasd.Position = WASD_POS containersById.space.Position = SPACE_POS containersById.shift.Position = SHIFT_POS clearAddedKeys() end ---------------------------------------------------------------- -- PRESS / RELEASE ---------------------------------------------------------------- local function cancelAllPresses() for inputObj, entry in pairs(pressState) do entry.button.BackgroundColor3 = CONFIG.BaseColor if entry.id ~= "Shift" and held[entry.id] ~= nil then held[entry.id] = false end pressState[inputObj] = nil end end local function findContainerOf(button) local p = button.Parent while p and p ~= gui do if containers[p] then return p end p = p.Parent end return nil end local function bindKey(button, keyId) button.InputBegan:Connect(function(input) if input.UserInputType ~= Enum.UserInputType.MouseButton1 and input.UserInputType ~= Enum.UserInputType.Touch then return end pressState[input] = { button = button, id = keyId, wasDrag = false } button.BackgroundColor3 = CONFIG.PressedColor if held[keyId] ~= nil and keyId ~= "Shift" then held[keyId] = true end if setupMode and not dragState then local c = findContainerOf(button) if c then dragState = { input = input, container = c, startMouse = input.Position, startPos = c.Position, moved = false, } end end end) end bindKey(keyW, "W") bindKey(keyA, "A") bindKey(keyS, "S") bindKey(keyD, "D") bindKey(spaceKey, "Space") bindKey(shiftKey, "Shift") UIS.InputChanged:Connect(function(input) if not dragState then return end if input.UserInputType ~= Enum.UserInputType.MouseMovement and input.UserInputType ~= Enum.UserInputType.Touch then return end local delta = input.Position - dragState.startMouse if not dragState.moved and delta.Magnitude > 8 then dragState.moved = true cancelAllPresses() end if dragState.moved and setupMode then dragState.container.Position = UDim2.new( dragState.startPos.X.Scale, dragState.startPos.X.Offset + delta.X, dragState.startPos.Y.Scale, dragState.startPos.Y.Offset + delta.Y ) end end) UIS.InputEnded:Connect(function(input) local entry = pressState[input] if entry then local wasDrag = dragState and dragState.moved entry.button.BackgroundColor3 = CONFIG.BaseColor if entry.id == "Shift" then if not wasDrag then shiftLockOn = not shiftLockOn shiftKey.BackgroundColor3 = shiftLockOn and CONFIG.ActiveColor or CONFIG.BaseColor local char = player.Character local hum = char and char:FindFirstChildOfClass("Humanoid") if hum then hum.CameraOffset = shiftLockOn and Vector3.new(1.75,0,0) or Vector3.zero end end else if held[entry.id] ~= nil then held[entry.id] = false end local entryData = findAddedEntry(entry.button) if entryData and not wasDrag then if entryData.isInteract then for prompt in pairs(heldPrompts) do heldPrompts[prompt] = nil task.spawn(function() pcall(function() prompt:InputHoldEnd() end) end) end elseif entryData.isMouse then VIM:SendMouseButtonEvent(0, 0, entryData.code.Value, false, game, 0) else VIM:SendKeyEvent(false, entryData.code, false, game) end end end pressState[input] = nil end if dragState and input == dragState.input then dragState = nil end end) ---------------------------------------------------------------- -- SETUP UI ---------------------------------------------------------------- local setupUI = Instance.new("Frame") setupUI.Name = "SetupUI" setupUI.BackgroundTransparency = 1 setupUI.Size = UDim2.new(1, 0, 0, 120) setupUI.Position = UDim2.new(0, 0, 0, 0) setupUI.ZIndex = 10 setupUI.Parent = gui local hint = Instance.new("TextLabel") hint.Text = "Drag to position · Tap + to add · Tap DONE to lock" hint.Size = UDim2.new(0.9, 0, 0, 28) hint.Position = UDim2.new(0.5, 0, 0, 15) hint.AnchorPoint = Vector2.new(0.5, 0) hint.BackgroundTransparency = 1 hint.TextColor3 = Color3.fromRGB(230, 230, 240) hint.Font = Enum.Font.GothamBold hint.TextSize = isMobile and 15 or 14 hint.ZIndex = 10 hint.Parent = setupUI local function makeBtn(text, pos, color, parent) local b = Instance.new("TextButton") b.Text = text b.Size = UDim2.fromOffset(130, 44) b.Position = pos b.AnchorPoint = Vector2.new(0.5, 0.5) b.BackgroundColor3 = color b.TextColor3 = Color3.fromRGB(255, 255, 255) b.Font = Enum.Font.GothamBold b.TextSize = 18 b.ZIndex = 11 b.Parent = parent or setupUI local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, 10) c.Parent = b return b end local addBtn = makeBtn("+ ADD", UDim2.new(0.5, -160, 0, 60), CONFIG.PanelAccent) local saveBtn = makeBtn("SAVE", UDim2.new(0.5, 0, 0, 60), CONFIG.PanelAccent) local doneBtn = makeBtn("DONE", UDim2.new(0.5, 160, 0, 60), CONFIG.PanelGreen) ---------------------------------------------------------------- -- KEY PICKER ---------------------------------------------------------------- local function openKeyPicker() local picker = Instance.new("Frame") picker.Size = UDim2.new(0.9, 0, 0.7, 0) picker.Position = UDim2.fromScale(0.5, 0.5) picker.AnchorPoint = Vector2.new(0.5, 0.5) picker.BackgroundColor3 = CONFIG.PanelColor picker.ZIndex = 20 picker.Parent = gui local pc = Instance.new("UICorner") pc.CornerRadius = UDim.new(0, 14) pc.Parent = picker local title = Instance.new("TextLabel") title.Text = "Pick a key to add" title.Size = UDim2.new(1, 0, 0, 40) title.BackgroundTransparency = 1 title.TextColor3 = Color3.fromRGB(230, 230, 240) title.Font = Enum.Font.GothamBold title.TextSize = 18 title.ZIndex = 21 title.Parent = picker local close = Instance.new("TextB