Files
aya/client/common/content/scripts/Modules/BackpackScript.lua
2025-12-17 16:47:48 +00:00

1567 lines
47 KiB
Lua

-- Backpack Version 4.21
-- OnlyTwentyCharacters
-------------------
--| Exposed API |--
-------------------
local BackpackScript = {}
BackpackScript.OpenClose = nil -- Function to toggle open/close
BackpackScript.IsOpen = false
BackpackScript.StateChanged = Instance.new('BindableEvent') -- Fires after any open/close, passes IsNowOpen
---------------------
--| Configurables |--
---------------------
local ICON_SIZE = 60
local FONT_SIZE = Enum.FontSize.Size14
local ICON_BUFFER = 5
local BACKGROUND_FADE = 0.50
local BACKGROUND_COLOR = Color3.new(31/255, 31/255, 31/255)
local SLOT_DRAGGABLE_COLOR = Color3.new(49/255, 49/255, 49/255)
local SLOT_EQUIP_COLOR = Color3.new(90/255, 142/255, 233/255)
local SLOT_EQUIP_THICKNESS = 0.1 -- Relative
local SLOT_FADE_LOCKED = 0.50 -- Locked means undraggable
local SLOT_BORDER_COLOR = Color3.new(1, 1, 1) -- Appears when dragging
local TOOLTIP_BUFFER = 6
local TOOLTIP_HEIGHT = 16
local TOOLTIP_OFFSET = -25 -- From top
local ARROW_IMAGE_OPEN = 'ayaasset://textures/ui/Backpack_Open.png'
local ARROW_IMAGE_CLOSE = 'ayaasset://textures/ui/Backpack_Close.png'
local ARROW_SIZE = UDim2.new(0, 14, 0, 9)
local ARROW_HOTKEY = Enum.KeyCode.Backquote.Value --TODO: Hookup '~' too?
local ARROW_HOTKEY_STRING = '`'
local HOTBAR_SLOTS_FULL = 10
local HOTBAR_SLOTS_MINI = 3
local HOTBAR_SLOTS_WIDTH_CUTOFF = 1024 -- Anything smaller is MINI
local HOTBAR_OFFSET_FROMBOTTOM = -30 -- Offset to make room for the Health GUI
local INVENTORY_ROWS_FULL = 4
local INVENTORY_ROWS_MINI = 2
local INVENTORY_HEADER_SIZE = 40
--local TITLE_OFFSET = 20 -- From left side
--local TITLE_TEXT = "Backpack"
local SEARCH_BUFFER = 5
local SEARCH_WIDTH = 200
local SEARCH_TEXT = " Search"
local SEARCH_TEXT_OFFSET_FROMLEFT = 0
local SEARCH_BACKGROUND_COLOR = Color3.new(0.37, 0.37, 0.37)
local SEARCH_BACKGROUND_FADE = 0.15
local DOUBLE_CLICK_TIME = 0.5
-----------------
--| Variables |--
-----------------
local PlayersService = game:GetService('Players')
local UserInputService = game:GetService('UserInputService')
local StarterGui = game:GetService('StarterGui')
local GuiService = game:GetService('GuiService')
local CoreGui = game:GetService('CoreGui')
local ContextActionService = game:GetService('ContextActionService')
local RobloxGui = CoreGui:WaitForChild('RobloxGui')
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
local utility = require(RobloxGui.Modules.Utility)
local topbarEnabled = true
if isTenFootInterface then
ICON_SIZE = 100
FONT_SIZE = Enum.FontSize.Size24
end
local gamepadActionsBound = false
local IS_PHONE = UserInputService.TouchEnabled and GuiService:GetScreenResolution().X < HOTBAR_SLOTS_WIDTH_CUTOFF
local HOTBAR_SLOTS = (IS_PHONE) and HOTBAR_SLOTS_MINI or HOTBAR_SLOTS_FULL
local HOTBAR_SIZE = UDim2.new(0, ICON_BUFFER + (HOTBAR_SLOTS * (ICON_SIZE + ICON_BUFFER)), 0, ICON_BUFFER + ICON_SIZE + ICON_BUFFER)
local ZERO_KEY_VALUE = Enum.KeyCode.Zero.Value
local DROP_HOTKEY_VALUE = Enum.KeyCode.Backspace.Value
local INVENTORY_ROWS = (IS_PHONE) and INVENTORY_ROWS_MINI or INVENTORY_ROWS_FULL
local Player = PlayersService.LocalPlayer
local MainFrame = nil
local HotbarFrame = nil
local InventoryFrame = nil
local ScrollingFrame = nil
local Character = nil
local Humanoid = nil
local Backpack = nil
local Slots = {} -- List of all Slots by index
local LowestEmptySlot = nil
local SlotsByTool = {} -- Map of Tools to their assigned Slots
local HotkeyFns = {} -- Map of KeyCode values to their assigned behaviors
local Dragging = {} -- Only used to check if anything is being dragged, to disable other input
local FullHotbarSlots = 0
local UpdateArrowFrame = nil -- Function defined in arrow init logic at bottom
local ActiveHopper = nil --NOTE: HopperBin
local StarterToolFound = false -- Special handling is required for the gear currently equipped on the site
local WholeThingEnabled = false
local TextBoxFocused = false -- ANY TextBox, not just the search box
local ResultsIndices = nil -- Results of a search, or nil
local HotkeyStrings = {} -- Used for eating/releasing hotkeys
local CharConns = {} -- Holds character connections to be cleared later
local TopBarEnabled = false
local GamepadEnabled = false -- determines if our gui needs to be gamepad friendly
local lastEquippedSlot = nil
-----------------
--| Functions |--
-----------------
local function NewGui(className, objectName)
local newGui = Instance.new(className)
newGui.Name = objectName
newGui.BackgroundColor3 = Color3.new(0, 0, 0)
newGui.BackgroundTransparency = 1
newGui.BorderColor3 = Color3.new(0, 0, 0)
newGui.BorderSizePixel = 0
newGui.Size = UDim2.new(1, 0, 1, 0)
if className:match('Text') then
newGui.TextColor3 = Color3.new(1, 1, 1)
newGui.Text = ''
newGui.Font = Enum.Font.SourceSans
newGui.FontSize = FONT_SIZE
newGui.TextWrapped = true
if className == 'TextButton' then
newGui.Font = Enum.Font.SourceSansBold
newGui.BorderSizePixel = 1
end
end
return newGui
end
local function FindLowestEmpty()
for i = 1, HOTBAR_SLOTS do
local slot = Slots[i]
if not slot.Tool then
return slot
end
end
return nil
end
local function AdjustHotbarFrames()
local inventoryOpen = InventoryFrame.Visible -- (Show all)
local visualTotal = (inventoryOpen) and HOTBAR_SLOTS or FullHotbarSlots
local visualIndex = 0
for i = 1, HOTBAR_SLOTS do
local slot = Slots[i]
if slot.Tool or inventoryOpen then
visualIndex = visualIndex + 1
slot:Readjust(visualIndex, visualTotal)
slot.Frame.Visible = true
else
slot.Frame.Visible = false
end
end
end
local function CheckBounds(guiObject, x, y)
local pos = guiObject.AbsolutePosition
local size = guiObject.AbsoluteSize
return (x > pos.X and x <= pos.X + size.X and y > pos.Y and y <= pos.Y + size.Y)
end
local function GetOffset(guiObject, point)
local centerPoint = guiObject.AbsolutePosition + (guiObject.AbsoluteSize / 2)
return (centerPoint - point).magnitude
end
local function DisableActiveHopper() --NOTE: HopperBin
ActiveHopper:ToggleSelect()
SlotsByTool[ActiveHopper]:UpdateEquipView()
ActiveHopper = nil
end
local function UnequipAllTools() --NOTE: HopperBin
if Humanoid then
Humanoid:UnequipTools()
if ActiveHopper then
DisableActiveHopper()
end
end
end
local function EquipNewTool(tool) --NOTE: HopperBin
UnequipAllTools()
if tool:IsA('HopperBin') then
tool:ToggleSelect()
SlotsByTool[tool]:UpdateEquipView()
ActiveHopper = tool
else
--Humanoid:EquipTool(tool) --NOTE: This would also unequip current Tool
tool.Parent = Character --TODO: Switch back to above line after EquipTool is fixed!
end
end
local function IsEquipped(tool)
return tool and ((tool:IsA('HopperBin') and tool.Active) or tool.Parent == Character) --NOTE: HopperBin
end
local function MakeSlot(parent, index)
index = index or (#Slots + 1)
-- Slot Definition --
local slot = {}
slot.Tool = nil
slot.Index = index
slot.Frame = nil
local SlotFrame = nil
local ToolIcon = nil
local ToolName = nil
local ToolChangeConn = nil
local HighlightFrame = nil
--NOTE: The following are only defined for Hotbar Slots
local ToolTip = nil
local SlotNumber = nil
-- Slot Functions --
local function UpdateSlotFading()
SlotFrame.BackgroundTransparency = (SlotFrame.Draggable) and 0 or SLOT_FADE_LOCKED
SlotFrame.BackgroundColor3 = (SlotFrame.Draggable) and SLOT_DRAGGABLE_COLOR or BACKGROUND_COLOR
end
function slot:Reposition()
-- Slots are positioned into rows
local index = (ResultsIndices and ResultsIndices[self]) or self.Index
local sizePlus = ICON_BUFFER + ICON_SIZE
local modSlots = 0
modSlots = ((index - 1) % HOTBAR_SLOTS) + 1
local row = 0
row = (index > HOTBAR_SLOTS) and (math.floor((index - 1) / HOTBAR_SLOTS)) - 1 or 0
SlotFrame.Position = UDim2.new(0, ICON_BUFFER + ((modSlots - 1) * sizePlus), 0, ICON_BUFFER + (sizePlus * row))
end
function slot:Readjust(visualIndex, visualTotal) --NOTE: Only used for Hotbar slots
local centered = HOTBAR_SIZE.X.Offset / 2
local sizePlus = ICON_BUFFER + ICON_SIZE
local midpointish = (visualTotal / 2) + 0.5
local factor = visualIndex - midpointish
SlotFrame.Position = UDim2.new(0, centered - (ICON_SIZE / 2) + (sizePlus * factor), 0, ICON_BUFFER)
end
function slot:Fill(tool)
if not tool then
return self:Clear()
end
self.Tool = tool
local function assignToolData()
local icon = tool.TextureId
ToolIcon.Image = icon
ToolName.Text = (icon == '') and tool.Name or '' -- (Only show name if no icon)
if ToolTip and tool:IsA('Tool') then --NOTE: HopperBin
ToolTip.Text = tool.ToolTip
local width = ToolTip.TextBounds.X + TOOLTIP_BUFFER
ToolTip.Size = UDim2.new(0, width, 0, TOOLTIP_HEIGHT)
ToolTip.Position = UDim2.new(0.5, -width / 2, 0, TOOLTIP_OFFSET)
end
end
assignToolData()
if ToolChangeConn then
ToolChangeConn:disconnect()
ToolChangeConn = nil
end
ToolChangeConn = tool.Changed:connect(function(property)
if property == 'TextureId' or property == 'Name' or property == 'ToolTip' then
assignToolData()
end
end)
local hotbarSlot = (self.Index <= HOTBAR_SLOTS)
local inventoryOpen = InventoryFrame.Visible
if not hotbarSlot or inventoryOpen then
SlotFrame.Draggable = true
end
self:UpdateEquipView()
if hotbarSlot then
FullHotbarSlots = FullHotbarSlots + 1
end
SlotsByTool[tool] = self
LowestEmptySlot = FindLowestEmpty()
UpdateArrowFrame()
end
function slot:Clear()
if not self.Tool then return end
if ToolChangeConn then
ToolChangeConn:disconnect()
ToolChangeConn = nil
end
ToolIcon.Image = ''
ToolName.Text = ''
if ToolTip then
ToolTip.Text = ''
ToolTip.Visible = false
end
SlotFrame.Draggable = false
self:UpdateEquipView(true) -- Show as unequipped
if self.Index <= HOTBAR_SLOTS then
FullHotbarSlots = FullHotbarSlots - 1
end
SlotsByTool[self.Tool] = nil
self.Tool = nil
LowestEmptySlot = FindLowestEmpty()
UpdateArrowFrame()
end
function slot:UpdateEquipView(unequippedOverride)
if not unequippedOverride and IsEquipped(self.Tool) then -- Equipped
lastEquippedSlot = slot
if not HighlightFrame then
HighlightFrame = NewGui('Frame', 'Equipped')
HighlightFrame.ZIndex = SlotFrame.ZIndex
local t = SLOT_EQUIP_THICKNESS
local dataTable = { -- Relative sizes and positions
{t, 1, 0, 0},
{1, t, 0, 0},
{t, 1, 1 - t, 0},
{1, t, 0, 1 - t},
}
for _, data in pairs(dataTable) do
local edgeFrame = NewGui('Frame', 'Edge')
edgeFrame.BackgroundTransparency = 0
edgeFrame.BackgroundColor3 = SLOT_EQUIP_COLOR
edgeFrame.Size = UDim2.new(data[1], 0, data[2], 0)
edgeFrame.Position = UDim2.new(data[3], 0, data[4], 0)
edgeFrame.ZIndex = HighlightFrame.ZIndex
edgeFrame.Parent = HighlightFrame
end
end
HighlightFrame.Parent = SlotFrame
else -- In the Backpack
if HighlightFrame then
HighlightFrame.Parent = nil
end
end
UpdateSlotFading()
end
function slot:IsEquipped()
return IsEquipped(self.Tool)
end
function slot:Delete()
SlotFrame:Destroy() --NOTE: Also clears connections
table.remove(Slots, self.Index)
local newSize = #Slots
-- Now adjust the rest (both visually and representationally)
for i = self.Index, newSize do
Slots[i]:SlideBack()
end
if newSize % HOTBAR_SLOTS == 0 then -- We lost a row at the bottom! Adjust the CanvasSize
local lastSlot = Slots[newSize]
local lowestPoint = lastSlot.Frame.Position.Y.Offset + lastSlot.Frame.Size.Y.Offset
ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, lowestPoint + ICON_BUFFER)
end
end
function slot:Swap(targetSlot) --NOTE: This slot (self) must not be empty!
local myTool, otherTool = self.Tool, targetSlot.Tool
self:Clear()
if otherTool then -- (Target slot might be empty)
targetSlot:Clear()
self:Fill(otherTool)
end
if myTool then
targetSlot:Fill(myTool)
else
targetSlot:Clear()
end
end
function slot:SlideBack() -- For inventory slot shifting
self.Index = self.Index - 1
SlotFrame.Name = self.Index
self:Reposition()
end
function slot:TurnNumber(on)
if SlotNumber then
SlotNumber.Visible = on
end
end
function slot:SetClickability(on) -- (Happens on open/close arrow)
if self.Tool then
SlotFrame.Draggable = not on
UpdateSlotFading()
end
end
function slot:CheckTerms(terms)
local hits = 0
local function checkEm(str, term)
local _, n = str:lower():gsub(term, '')
hits = hits + n
end
local tool = self.Tool
for term in pairs(terms) do
checkEm(tool.Name, term)
if tool:IsA('Tool') then --NOTE: HopperBin
checkEm(tool.ToolTip, term)
end
end
return hits
end
-- Slot Init Logic --
SlotFrame = NewGui('TextButton', index)
SlotFrame.BackgroundColor3 = BACKGROUND_COLOR
SlotFrame.BorderColor3 = SLOT_BORDER_COLOR
SlotFrame.Text = ""
SlotFrame.AutoButtonColor = false
SlotFrame.BorderSizePixel = 0
SlotFrame.Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE)
SlotFrame.Active = true
SlotFrame.Draggable = false
SlotFrame.BackgroundTransparency = SLOT_FADE_LOCKED
SlotFrame.MouseButton1Click:connect(function() changeSlot(slot) end)
slot.Frame = SlotFrame
ToolIcon = NewGui('ImageLabel', 'Icon')
ToolIcon.Size = UDim2.new(0.8, 0, 0.8, 0)
ToolIcon.Position = UDim2.new(0.1, 0, 0.1, 0)
ToolIcon.Parent = SlotFrame
ToolName = NewGui('TextLabel', 'ToolName')
ToolName.Size = UDim2.new(1, -2, 1, -2)
ToolName.Position = UDim2.new(0, 1, 0, 1)
ToolName.Parent = SlotFrame
slot:Reposition()
if index <= HOTBAR_SLOTS then -- Hotbar-Specific Slot Stuff
-- ToolTip stuff
ToolTip = NewGui('TextLabel', 'ToolTip')
ToolTip.TextWrapped = false
ToolTip.TextYAlignment = Enum.TextYAlignment.Top
ToolTip.BackgroundColor3 = Color3.new(0.4, 0.4, 0.4)
ToolTip.BackgroundTransparency = 0
ToolTip.Visible = false
ToolTip.Parent = SlotFrame
SlotFrame.MouseEnter:connect(function()
if ToolTip.Text ~= '' then
ToolTip.Visible = true
end
end)
SlotFrame.MouseLeave:connect(function() ToolTip.Visible = false end)
-- Slot select logic, activated by clicking or pressing hotkey
function slot:Select()
local tool = slot.Tool
if tool then
if IsEquipped(tool) then --NOTE: HopperBin
UnequipAllTools()
elseif tool.Parent == Backpack then
EquipNewTool(tool)
end
end
end
function slot:MoveToInventory()
if slot.Index <= HOTBAR_SLOTS then -- From a Hotbar slot
local tool = slot.Tool
self:Clear() --NOTE: Order matters here
local newSlot = MakeSlot(ScrollingFrame)
newSlot:Fill(tool)
if IsEquipped(tool) then -- Also unequip it --NOTE: HopperBin
UnequipAllTools()
end
-- Also hide the inventory slot if we're showing results right now
if ResultsIndices then
newSlot.Frame.Visible = false
end
end
end
-- Show label and assign hotkeys for 1-9 and 0 (zero is always last slot when > 10 total)
if index < 10 or index == HOTBAR_SLOTS then -- NOTE: Hardcoded on purpose!
local slotNum = (index < 10) and index or 0
SlotNumber = NewGui('TextLabel', 'Number')
SlotNumber.Text = slotNum
SlotNumber.Size = UDim2.new(0.15, 0, 0.15, 0)
SlotNumber.Visible = false
SlotNumber.Parent = SlotFrame
HotkeyFns[ZERO_KEY_VALUE + slotNum] = slot.Select
end
else -- Inventory-Specific Slot Stuff
local newRow = false
newRow = (index % HOTBAR_SLOTS == 1)
if newRow then -- We are the first slot of a new row! Adjust the CanvasSize
local lowestPoint = SlotFrame.Position.Y.Offset + SlotFrame.Size.Y.Offset
ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, lowestPoint + ICON_BUFFER)
end
-- Scroll to new inventory slot, if we're open and not viewing search results
if InventoryFrame.Visible and not ResultsIndices then
local offset = ScrollingFrame.CanvasSize.Y.Offset - ScrollingFrame.AbsoluteSize.Y
ScrollingFrame.CanvasPosition = Vector2.new(0, math.max(0, offset))
end
end
do -- Dragging Logic
local startPoint = SlotFrame.Position
local lastUpTime = 0
local startParent = nil
SlotFrame.DragBegin:connect(function(dragPoint)
Dragging[SlotFrame] = true
startPoint = dragPoint
SlotFrame.BorderSizePixel = 2
-- Raise above other slots
SlotFrame.ZIndex = 2
ToolIcon.ZIndex = 2
ToolName.ZIndex = 2
if SlotNumber then
SlotNumber.ZIndex = 2
end
if HighlightFrame then
HighlightFrame.ZIndex = 2
for _, child in pairs(HighlightFrame:GetChildren()) do
child.ZIndex = 2
end
end
-- Circumvent the ScrollingFrame's ClipsDescendants property
startParent = SlotFrame.Parent
if startParent == ScrollingFrame then
SlotFrame.Parent = InventoryFrame
local pos = ScrollingFrame.Position
local offset = ScrollingFrame.CanvasPosition - Vector2.new(pos.X.Offset, pos.Y.Offset)
SlotFrame.Position = SlotFrame.Position - UDim2.new(0, offset.X, 0, offset.Y)
end
end)
SlotFrame.DragStopped:connect(function(x, y)
local now = tick()
SlotFrame.Position = startPoint
SlotFrame.Parent = startParent
SlotFrame.BorderSizePixel = 0
-- Restore height
SlotFrame.ZIndex = 1
ToolIcon.ZIndex = 1
ToolName.ZIndex = 1
if SlotNumber then
SlotNumber.ZIndex = 1
end
if HighlightFrame then
HighlightFrame.ZIndex = 1
for _, child in pairs(HighlightFrame:GetChildren()) do
child.ZIndex = 1
end
end
Dragging[SlotFrame] = nil
-- Make sure the tool wasn't dropped
if not slot.Tool then
return
end
-- Check where we were dropped
if CheckBounds(InventoryFrame, x, y) then
if slot.Index <= HOTBAR_SLOTS then
slot:MoveToInventory()
end
-- Check for double clicking on an inventory slot, to move into empty hotbar slot
if slot.Index > HOTBAR_SLOTS and now - lastUpTime < DOUBLE_CLICK_TIME then
if LowestEmptySlot then
local myTool = slot.Tool
slot:Clear()
LowestEmptySlot:Fill(myTool)
slot:Delete()
end
now = 0 -- Resets the timer
end
elseif CheckBounds(HotbarFrame, x, y) then
local closest = {math.huge, nil}
for i = 1, HOTBAR_SLOTS do
local otherSlot = Slots[i]
local offset = GetOffset(otherSlot.Frame, Vector2.new(x, y))
if offset < closest[1] then
closest = {offset, otherSlot}
end
end
local closestSlot = closest[2]
if closestSlot ~= slot then
slot:Swap(closestSlot)
if slot.Index > HOTBAR_SLOTS then
local tool = slot.Tool
if not tool then -- Clean up after ourselves if we're an inventory slot that's now empty
slot:Delete()
else -- Moved inventory slot to hotbar slot, and gained a tool that needs to be unequipped
if IsEquipped(tool) then --NOTE: HopperBin
UnequipAllTools()
end
-- Also hide the inventory slot if we're showing results right now
if ResultsIndices then
slot.Frame.Visible = false
end
end
end
end
else
-- local tool = slot.Tool
-- if tool.CanBeDropped then --TODO: HopperBins
-- tool.Parent = workspace
-- --TODO: Move away from character
-- end
if slot.Index <= HOTBAR_SLOTS then
slot:MoveToInventory() --NOTE: Temporary
end
end
lastUpTime = now
end)
end
-- All ready!
SlotFrame.Parent = parent
Slots[index] = slot
return slot
end
local function OnChildAdded(child) -- To Character or Backpack
if not child:IsA('Tool') and not child:IsA('HopperBin') then --NOTE: HopperBin
if child:IsA('Humanoid') and child.Parent == Character then
Humanoid = child
end
return
end
local tool = child
if ActiveHopper and tool.Parent == Character then --NOTE: HopperBin
DisableActiveHopper()
end
--TODO: Optimize / refactor / do something else
if not StarterToolFound and tool.Parent == Character and not SlotsByTool[tool] then
local starterGear = Player:FindFirstChild('StarterGear')
if starterGear then
if starterGear:FindFirstChild(tool.Name) then
StarterToolFound = true
local slot = LowestEmptySlot or MakeSlot(ScrollingFrame)
for i = slot.Index, 1, -1 do
local curr = Slots[i] -- An empty slot, because above
local pIndex = i - 1
if pIndex > 0 then
local prev = Slots[pIndex] -- Guaranteed to be full, because above
prev:Swap(curr)
else
curr:Fill(tool)
end
end
-- Have to manually unequip a possibly equipped tool
for _, child in pairs(Character:GetChildren()) do
if child:IsA('Tool') and child ~= tool then
child.Parent = Backpack
end
end
AdjustHotbarFrames()
return -- We're done here
end
end
end
-- The tool is either moving or new
local slot = SlotsByTool[tool]
if slot then
slot:UpdateEquipView()
else -- New! Put into lowest hotbar slot or new inventory slot
slot = LowestEmptySlot or MakeSlot(ScrollingFrame)
slot:Fill(tool)
if slot.Index <= HOTBAR_SLOTS and not InventoryFrame.Visible then
AdjustHotbarFrames()
end
if tool:IsA('HopperBin') then --NOTE: HopperBin
if tool.Active then
UnequipAllTools()
ActiveHopper = tool
end
end
end
end
local function OnChildRemoved(child) -- From Character or Backpack
if not child:IsA('Tool') and not child:IsA('HopperBin') then --NOTE: HopperBin
return
end
local tool = child
-- Ignore this event if we're just moving between the two
local newParent = tool.Parent
if newParent == Character or newParent == Backpack then
return
end
local slot = SlotsByTool[tool]
if slot then
slot:Clear()
if slot.Index > HOTBAR_SLOTS then -- Inventory slot
slot:Delete()
elseif not InventoryFrame.Visible then
AdjustHotbarFrames()
end
end
if tool == ActiveHopper then --NOTE: HopperBin
ActiveHopper = nil
end
end
local function OnCharacterAdded(character)
-- First, clean up any old slots
for i = #Slots, 1, -1 do
local slot = Slots[i]
if slot.Tool then
slot:Clear()
end
if i > HOTBAR_SLOTS then
slot:Delete()
end
end
ActiveHopper = nil --NOTE: HopperBin
-- And any old connections
for _, conn in pairs(CharConns) do
conn:disconnect()
end
CharConns = {}
-- Hook up the new character
Character = character
table.insert(CharConns, character.ChildRemoved:connect(OnChildRemoved))
table.insert(CharConns, character.ChildAdded:connect(OnChildAdded))
for _, child in pairs(character:GetChildren()) do
OnChildAdded(child)
end
--NOTE: Humanoid is set inside OnChildAdded
-- And the new backpack, when it gets here
Backpack = Player:WaitForChild('Backpack')
table.insert(CharConns, Backpack.ChildRemoved:connect(OnChildRemoved))
table.insert(CharConns, Backpack.ChildAdded:connect(OnChildAdded))
for _, child in pairs(Backpack:GetChildren()) do
OnChildAdded(child)
end
AdjustHotbarFrames()
end
local function OnInputBegan(input, isProcessed)
-- Pass through keyboard hotkeys when not typing into a TextBox and not disabled (except for the Drop key)
if input.UserInputType == Enum.UserInputType.Keyboard and not TextBoxFocused and (WholeThingEnabled or input.KeyCode.Value == DROP_HOTKEY_VALUE) then
print(HotkeyFns[input.KeyCode.Value])
local hotkeyBehavior = HotkeyFns[input.KeyCode.Value]
if hotkeyBehavior then
hotkeyBehavior(isProcessed)
end
end
end
local function OnUISChanged(property)
if property == 'KeyboardEnabled' then
local on = UserInputService.KeyboardEnabled
for i = 1, HOTBAR_SLOTS do
Slots[i]:TurnNumber(on)
end
end
end
-------------------------
--| Gamepad Functions |--
-------------------------
local lastChangeToolInputObject = nil
local lastChangeToolInputTime = nil
local maxEquipDeltaTime = 0.06
local noOpFunc = function() end
local selectDirection = Vector2.new(0,0)
local hotbarVisible = false
function unbindAllGamepadEquipActions()
ContextActionService:UnbindCoreAction("RBXBackpackHasGamepadFocus")
ContextActionService:UnbindCoreAction("RBXCloseInventory")
end
local function setHotbarVisibility(visible, isInventoryScreen)
for i = 1, HOTBAR_SLOTS do
local hotbarSlot = Slots[i]
if hotbarSlot and hotbarSlot.Frame and (isInventoryScreen or hotbarSlot.Tool) then
hotbarSlot.Frame.Visible = visible
end
end
end
local function getInputDirection(inputObject)
local buttonModifier = 1
if inputObject.UserInputState == Enum.UserInputState.End then
buttonModifier = -1
end
if inputObject.KeyCode == Enum.KeyCode.Thumbstick1 then
local magnitude = inputObject.Position.magnitude
if magnitude > 0.98 then
local normalizedVector = Vector2.new(inputObject.Position.x / magnitude, -inputObject.Position.y / magnitude)
selectDirection = normalizedVector
else
selectDirection = Vector2.new(0,0)
end
elseif inputObject.KeyCode == Enum.KeyCode.DPadLeft then
selectDirection = Vector2.new(selectDirection.x - 1 * buttonModifier, selectDirection.y)
elseif inputObject.KeyCode == Enum.KeyCode.DPadRight then
selectDirection = Vector2.new(selectDirection.x + 1 * buttonModifier, selectDirection.y)
elseif inputObject.KeyCode == Enum.KeyCode.DPadUp then
selectDirection = Vector2.new(selectDirection.x, selectDirection.y - 1 * buttonModifier)
elseif inputObject.KeyCode == Enum.KeyCode.DPadDown then
selectDirection = Vector2.new(selectDirection.x, selectDirection.y + 1 * buttonModifier)
else
selectDirection = Vector2.new(0,0)
end
return selectDirection
end
local selectToolExperiment = function(actionName, inputState, inputObject)
local inputDirection = getInputDirection(inputObject)
if inputDirection == Vector2.new(0,0) then
return
end
local angle = math.atan2(inputDirection.y, inputDirection.x) - math.atan2(-1, 0)
if angle < 0 then
angle = angle + (math.pi * 2)
end
local quarterPi = (math.pi * 0.25)
local index = (angle/quarterPi) + 1
index = math.floor(index + 0.5) -- round index to whole number
if index > HOTBAR_SLOTS then
index = 1
end
if index > 0 then
local selectedSlot = Slots[index]
if selectedSlot and selectedSlot.Tool and not selectedSlot:IsEquipped() then
selectedSlot:Select()
end
else
UnequipAllTools()
end
end
local changeToolFunc = function(actionName, inputState, inputObject)
if inputState ~= Enum.UserInputState.Begin then return end
if lastChangeToolInputObject then
if (lastChangeToolInputObject.KeyCode == Enum.KeyCode.ButtonR1 and
inputObject.KeyCode == Enum.KeyCode.ButtonL1) or
(lastChangeToolInputObject.KeyCode == Enum.KeyCode.ButtonL1 and
inputObject.KeyCode == Enum.KeyCode.ButtonR1) then
if (tick() - lastChangeToolInputTime) <= maxEquipDeltaTime then
UnequipAllTools()
lastChangeToolInputObject = inputObject
lastChangeToolInputTime = tick()
return
end
end
end
lastChangeToolInputObject = inputObject
lastChangeToolInputTime = tick()
delay(maxEquipDeltaTime, function()
if lastChangeToolInputObject ~= inputObject then return end
local moveDirection = 0
if (inputObject.KeyCode == Enum.KeyCode.ButtonL1) then
moveDirection = -1
else
moveDirection = 1
end
for i = 1, HOTBAR_SLOTS do
local hotbarSlot = Slots[i]
if hotbarSlot:IsEquipped() then
local newSlotPosition = moveDirection + i
if newSlotPosition > HOTBAR_SLOTS then
newSlotPosition = 1
elseif newSlotPosition < 1 then
newSlotPosition = HOTBAR_SLOTS
end
local origNewSlotPos = newSlotPosition
while not Slots[newSlotPosition].Tool do
newSlotPosition = newSlotPosition + moveDirection
if newSlotPosition == origNewSlotPos then return end
if newSlotPosition > HOTBAR_SLOTS then
newSlotPosition = 1
elseif newSlotPosition < 1 then
newSlotPosition = HOTBAR_SLOTS
end
end
Slots[newSlotPosition]:Select()
return
end
end
if lastEquippedSlot and lastEquippedSlot.Tool then
lastEquippedSlot:Select()
return
end
for i = 1, HOTBAR_SLOTS do
if Slots[i].Tool then
Slots[i]:Select()
return
end
end
end)
end
function getGamepadSwapSlot()
for i = 1, #Slots do
if Slots[i].Frame.BorderSizePixel > 0 then
return Slots[i]
end
end
end
function changeSlot(slot)
if slot.Frame == GuiService.SelectedCoreObject then
local currentlySelectedSlot = getGamepadSwapSlot()
if currentlySelectedSlot then
currentlySelectedSlot.Frame.BorderSizePixel = 0
if currentlySelectedSlot ~= slot then
slot:Swap(currentlySelectedSlot)
if slot.Index > HOTBAR_SLOTS and not slot.Tool then
if GuiService.SelectedCoreObject == slot.Frame then
GuiService.SelectedCoreObject = currentlySelectedSlot.Frame
end
slot:Delete()
end
if currentlySelectedSlot.Index > HOTBAR_SLOTS and not currentlySelectedSlot.Tool then
if GuiService.SelectedCoreObject == currentlySelectedSlot.Frame then
GuiService.SelectedCoreObject = slot.Frame
end
currentlySelectedSlot:Delete()
end
end
else
local startSize = slot.Frame.Size
local startPosition = slot.Frame.Position
slot.Frame:TweenSizeAndPosition(startSize + UDim2.new(0, 10, 0, 10), startPosition - UDim2.new(0, 5, 0, 5), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, .1, true, function() slot.Frame:TweenSizeAndPosition(startSize, startPosition, Enum.EasingDirection.In, Enum.EasingStyle.Quad, .1, true) end)
slot.Frame.BorderSizePixel = 3
end
else
slot:Select()
end
end
function enableGamepadInventoryControl()
local goBackOneLevel = function(actionName, inputState, inputObject)
if inputState ~= Enum.UserInputState.Begin then return end
local selectedSlot = getGamepadSwapSlot()
if selectedSlot then
local selectedSlot = getGamepadSwapSlot()
if selectedSlot then
selectedSlot.Frame.BorderSizePixel = 0
return
end
elseif InventoryFrame.Visible then
BackpackScript.OpenClose()
spawn(function() GuiService:SetMenuIsOpen(false) end)
end
end
ContextActionService:BindCoreAction("RBXBackpackHasGamepadFocus", noOpFunc, false, Enum.UserInputType.Gamepad1)
ContextActionService:BindCoreAction("RBXCloseInventory", goBackOneLevel, false, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonStart)
GuiService.SelectedCoreObject = HotbarFrame:FindFirstChild("1")
end
function disableGamepadInventoryControl()
unbindAllGamepadEquipActions()
for i = 1, HOTBAR_SLOTS do
local hotbarSlot = Slots[i]
if hotbarSlot and hotbarSlot.Frame then
hotbarSlot.Frame.BorderSizePixel = 0
end
end
if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject:IsDescendantOf(MainFrame) then
GuiService.SelectedCoreObject = nil
end
end
function gamepadDisconnected()
GamepadEnabled = false
disableGamepadInventoryControl()
end
function gamepadConnected()
GamepadEnabled = true
GuiService:AddSelectionParent("RBXBackpackSelection", MainFrame)
if not gamepadActionsBound then
gamepadActionsBound = true
ContextActionService:BindCoreAction("RBXHotbarEquip", changeToolFunc, false, Enum.KeyCode.ButtonL1, Enum.KeyCode.ButtonR1)
end
if InventoryFrame.Visible then
enableGamepadInventoryControl()
end
end
-----------------------------
--| End Gamepad Functions |--
-----------------------------
local function OnCoreGuiChanged(coreGuiType, enabled)
-- Check for enabling/disabling the whole thing
if coreGuiType == Enum.CoreGuiType.Backpack or coreGuiType == Enum.CoreGuiType.All then
enabled = enabled and topbarEnabled
WholeThingEnabled = enabled
MainFrame.Visible = enabled
-- Eat/Release hotkeys (Doesn't affect UserInputService)
for _, keyString in pairs(HotkeyStrings) do
if enabled then
GuiService:AddKey(keyString)
else
GuiService:RemoveKey(keyString)
end
end
if GamepadEnabled then
if enabled then
gamepadActionsBound = true
ContextActionService:BindCoreAction("RBXHotbarEquip", changeToolFunc, false, Enum.KeyCode.ButtonL1, Enum.KeyCode.ButtonR1)
else
disableGamepadInventoryControl()
gamepadActionsBound = false
ContextActionService:UnbindCoreAction("RBXHotbarEquip")
end
end
end
-- Also check if the Health GUI is showing, and shift everything down (or back up) accordingly
if not TopBarEnabled and (coreGuiType == Enum.CoreGuiType.Health or coreGuiType == Enum.CoreGuiType.All) then
MainFrame.Position = UDim2.new(0, 0, 0, enabled and HOTBAR_OFFSET_FROMBOTTOM or 0)
end
end
--------------------
--| Script Logic |--
--------------------
-- First check if the TopBar is enabled. This affects the ArrowFrame existence and MainFrame position
pcall(function() TopBarEnabled = settings():GetFFlag('UseInGameTopBar') end)
-- Make the main frame, which (mostly) covers the screen
MainFrame = NewGui('Frame', 'Backpack')
MainFrame.Visible = false
MainFrame.Parent = RobloxGui
-- Make the HotbarFrame, which holds only the Hotbar Slots
HotbarFrame = NewGui('Frame', 'Hotbar')
HotbarFrame.Size = HOTBAR_SIZE
HotbarFrame.Position = UDim2.new(0.5, -HotbarFrame.Size.X.Offset / 2, 1, -HotbarFrame.Size.Y.Offset)
HotbarFrame.Parent = MainFrame
-- Make all the Hotbar Slots
for i = 1, HOTBAR_SLOTS do
local slot = MakeSlot(HotbarFrame, i)
slot.Frame.Visible = false
if not LowestEmptySlot then
LowestEmptySlot = slot
end
end
-- Make the Inventory, which holds the ScrollingFrame, the header, and the search box
InventoryFrame = NewGui('Frame', 'Inventory')
InventoryFrame.BackgroundTransparency = BACKGROUND_FADE
InventoryFrame.BackgroundColor3 = BACKGROUND_COLOR
InventoryFrame.Active = true
InventoryFrame.Size = UDim2.new(0, HotbarFrame.Size.X.Offset, 0, (HotbarFrame.Size.Y.Offset * INVENTORY_ROWS) + INVENTORY_HEADER_SIZE)
InventoryFrame.Position = UDim2.new(0.5, -InventoryFrame.Size.X.Offset / 2, 1, HotbarFrame.Position.Y.Offset - InventoryFrame.Size.Y.Offset)
InventoryFrame.Visible = false
InventoryFrame.Parent = MainFrame
-- Make the ScrollingFrame, which holds the rest of the Slots (however many)
ScrollingFrame = NewGui('ScrollingFrame', 'ScrollingFrame')
ScrollingFrame.Selectable = false
ScrollingFrame.Size = UDim2.new(1, ScrollingFrame.ScrollBarThickness + 1, 1, -INVENTORY_HEADER_SIZE)
ScrollingFrame.Position = UDim2.new(0, 0, 0, INVENTORY_HEADER_SIZE)
ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0)
ScrollingFrame.Parent = InventoryFrame
-- Make the header title, in the Inventory
--local headerText = NewGui('TextLabel', 'Header')
--headerText.Text = TITLE_TEXT
--headerText.TextXAlignment = Enum.TextXAlignment.Left
--headerText.Font = Enum.Font.SourceSansBold
--headerText.FontSize = Enum.FontSize.Size48
--headerText.TextStrokeColor3 = SLOT_EQUIP_COLOR
--headerText.TextStrokeTransparency = BACKGROUND_FADE
--headerText.Size = UDim2.new(0, (InventoryFrame.Size.X.Offset / 2) - TITLE_OFFSET, 0, INVENTORY_HEADER_SIZE)
--headerText.Position = UDim2.new(0, TITLE_OFFSET, 0, 0)
--headerText.Parent = InventoryFrame
--Make the gamepad hint frame
local gamepadHintsFrame = utility:Create'Frame'
{
Name = "GamepadHintsFrame",
Size = UDim2.new(0, HotbarFrame.Size.X.Offset, 0, (isTenFootInterface and 95 or 60)),
BackgroundTransparency = 1,
Visible = false,
Parent = MainFrame
}
local function addGamepadHint(hintImage, hintImageLarge, hintText)
local hintFrame = utility:Create'Frame'
{
Name = "HintFrame",
Size = UDim2.new(1, 0, 1, -5),
Position = UDim2.new(0, 0, 0, 0),
BackgroundTransparency = 1,
Parent = gamepadHintsFrame
}
local hintImage = utility:Create'ImageLabel'
{
Name = "HintImage",
Size = (isTenFootInterface and UDim2.new(0,90,0,90) or UDim2.new(0,60,0,60)),
BackgroundTransparency = 1,
Image = (isTenFootInterface and hintImageLarge or hintImage),
Parent = hintFrame
}
local hintText = utility:Create'TextLabel'
{
Name = "HintText",
Position = UDim2.new(0, (isTenFootInterface and 100 or 70), 0, 0),
Size = UDim2.new(1, -(isTenFootInterface and 100 or 70), 1, 0),
Font = Enum.Font.SourceSansBold,
FontSize = (isTenFootInterface and Enum.FontSize.Size36 or Enum.FontSize.Size24),
BackgroundTransparency = 1,
Text = hintText,
TextColor3 = Color3.new(1,1,1),
TextXAlignment = Enum.TextXAlignment.Left,
Parent = hintFrame
}
end
local function resizeGamepadHintsFrame()
gamepadHintsFrame.Size = UDim2.new(HotbarFrame.Size.X.Scale, HotbarFrame.Size.X.Offset, 0, (isTenFootInterface and 95 or 60))
gamepadHintsFrame.Position = UDim2.new(HotbarFrame.Position.X.Scale, HotbarFrame.Position.X.Offset, InventoryFrame.Position.Y.Scale, InventoryFrame.Position.Y.Offset - gamepadHintsFrame.Size.Y.Offset)
local spaceTaken = 0
local gamepadHints = gamepadHintsFrame:GetChildren()
--First get the total space taken by all the hints
for i = 1, #gamepadHints do
gamepadHints[i].Size = UDim2.new(1, 0, 1, -5)
gamepadHints[i].Position = UDim2.new(0, 0, 0, 0)
spaceTaken = spaceTaken + (gamepadHints[i].HintText.Position.X.Offset + gamepadHints[i].HintText.TextBounds.X)
end
--The space between all the frames should be equal
local spaceBetweenElements = (gamepadHintsFrame.AbsoluteSize.X - spaceTaken)/(#gamepadHints - 1)
for i = 1, #gamepadHints do
gamepadHints[i].Position = (i == 1 and UDim2.new(0, 0, 0, 0) or UDim2.new(0, gamepadHints[i-1].Position.X.Offset + gamepadHints[i-1].Size.X.Offset + spaceBetweenElements, 0, 0))
gamepadHints[i].Size = UDim2.new(0, (gamepadHints[i].HintText.Position.X.Offset + gamepadHints[i].HintText.TextBounds.X), 1, -5)
end
end
addGamepadHint("ayaasset://textures/ui/Settings/Help/XButtonDark.png", "ayaasset://textures/ui/Settings/Help/XButtonDark@2x.png", "Remove From Hotbar")
addGamepadHint("ayaasset://textures/ui/Settings/Help/AButtonDark.png", "ayaasset://textures/ui/Settings/Help/AButtonDark@2x.png", "Select/Swap")
addGamepadHint("ayaasset://textures/ui/Settings/Help/BButtonDark.png", "ayaasset://textures/ui/Settings/Help/BButtonDark@2x.png", "Close Backpack")
do -- Search stuff
local searchFrame = NewGui('Frame', 'Search')
searchFrame.BackgroundColor3 = SEARCH_BACKGROUND_COLOR
searchFrame.BackgroundTransparency = SEARCH_BACKGROUND_FADE
searchFrame.Size = UDim2.new(0, SEARCH_WIDTH - (SEARCH_BUFFER * 2), 0, INVENTORY_HEADER_SIZE - (SEARCH_BUFFER * 2))
searchFrame.Position = UDim2.new(1, -searchFrame.Size.X.Offset - SEARCH_BUFFER, 0, SEARCH_BUFFER)
searchFrame.Parent = InventoryFrame
local searchBox = NewGui('TextBox', 'TextBox')
searchBox.Text = SEARCH_TEXT
searchBox.ClearTextOnFocus = false
searchBox.FontSize = Enum.FontSize.Size24
searchBox.TextXAlignment = Enum.TextXAlignment.Left
searchBox.Size = searchFrame.Size - UDim2.new(0, SEARCH_TEXT_OFFSET_FROMLEFT, 0, 0)
searchBox.Position = UDim2.new(0, SEARCH_TEXT_OFFSET_FROMLEFT, 0, 0)
searchBox.Parent = searchFrame
local xButton = NewGui('TextButton', 'X')
xButton.Text = 'x'
xButton.TextColor3 = SLOT_EQUIP_COLOR
xButton.FontSize = Enum.FontSize.Size24
xButton.TextYAlignment = Enum.TextYAlignment.Bottom
xButton.BackgroundColor3 = SEARCH_BACKGROUND_COLOR
xButton.BackgroundTransparency = 0
xButton.Size = UDim2.new(0, searchFrame.Size.Y.Offset - (SEARCH_BUFFER * 2), 0, searchFrame.Size.Y.Offset - (SEARCH_BUFFER * 2))
xButton.Position = UDim2.new(1, -xButton.Size.X.Offset - (SEARCH_BUFFER * 2), 0.5, -xButton.Size.Y.Offset / 2)
xButton.ZIndex = 0
xButton.Visible = true
xButton.BorderSizePixel = 0
xButton.Parent = searchFrame
local function search()
local terms = {}
for word in searchBox.Text:gmatch('%S+') do
terms[word:lower()] = true
end
local hitTable = {}
for i = HOTBAR_SLOTS + 1, #Slots do -- Only search inventory slots
local slot = Slots[i]
local hits = slot:CheckTerms(terms)
table.insert(hitTable, {slot, hits})
slot.Frame.Visible = false
end
table.sort(hitTable, function(left, right)
return left[2] > right[2]
end)
ResultsIndices = {}
for i, data in ipairs(hitTable) do
local slot, hits = data[1], data[2]
if hits > 0 then
ResultsIndices[slot] = HOTBAR_SLOTS + i
slot:Reposition()
slot.Frame.Visible = true
end
end
ScrollingFrame.CanvasPosition = Vector2.new(0, 0)
xButton.ZIndex = 3
end
local function clearResults()
if xButton.ZIndex > 0 then
ResultsIndices = nil
for i = HOTBAR_SLOTS + 1, #Slots do
local slot = Slots[i]
slot:Reposition()
slot.Frame.Visible = true
end
xButton.ZIndex = 0
end
end
local function reset()
clearResults()
searchBox.Text = SEARCH_TEXT
end
local function onChanged(property)
if property == 'Text' then
local text = searchBox.Text
if text == '' then
clearResults()
elseif text ~= SEARCH_TEXT then
search()
end
end
end
local function onFocused()
if searchBox.Text == SEARCH_TEXT then
searchBox.Text = ''
end
end
local function focusLost(enterPressed)
if enterPressed then
--TODO: Could optimize
search()
elseif searchBox.Text == '' then
searchBox.Text = SEARCH_TEXT
end
end
searchBox.Focused:connect(onFocused)
xButton.MouseButton1Click:connect(reset)
searchBox.Changed:connect(onChanged)
searchBox.FocusLost:connect(focusLost)
BackpackScript.StateChanged.Event:connect(function(isNowOpen)
xButton.Modal = isNowOpen -- Allows free mouse movement even in first person
if not isNowOpen then
reset()
end
end)
HotkeyFns[Enum.KeyCode.Escape.Value] = function(isProcessed)
if isProcessed then -- Pressed from within a TextBox
reset()
elseif InventoryFrame.Visible then
BackpackScript.OpenClose()
end
end
local function detectGamepad(input, processed)
if input.UserInputType == Enum.UserInputType.Gamepad1 then
searchFrame.Visible = false
else
searchFrame.Visible = true
end
end
local uis = game:GetService("UserInputService")
uis.InputBegan:connect(detectGamepad)
uis.InputChanged:connect(detectGamepad)
end
do -- Make the Inventory expand/collapse arrow (unless TopBar)
local arrowFrame, arrowIcon = nil, nil, nil
local collapsed, closed, opened = nil, nil, nil
local removeHotBarSlot = function(name, state, input)
if state ~= Enum.UserInputState.Begin then return end
if not GuiService.SelectedCoreObject then return end
for i = 1, HOTBAR_SLOTS do
if Slots[i].Frame == GuiService.SelectedCoreObject and Slots[i].Tool then
Slots[i]:MoveToInventory()
return
end
end
end
local function openClose()
if not next(Dragging) then -- Only continue if nothing is being dragged
InventoryFrame.Visible = not InventoryFrame.Visible
local nowOpen = InventoryFrame.Visible
if arrowIcon then
arrowIcon.Image = (nowOpen) and ARROW_IMAGE_CLOSE or ARROW_IMAGE_OPEN
end
AdjustHotbarFrames()
UpdateArrowFrame()
HotbarFrame.Active = not HotbarFrame.Active
for i = 1, HOTBAR_SLOTS do
Slots[i]:SetClickability(not nowOpen)
end
end
if GamepadEnabled then
if InventoryFrame.Visible then
local lastInputType = UserInputService:GetLastInputType()
local currentlyUsingGamepad = (lastInputType == Enum.UserInputType.Gamepad1 or lastInputType == Enum.UserInputType.Gamepad2 or
lastInputType == Enum.UserInputType.Gamepad3 or lastInputType == Enum.UserInputType.Gamepad4)
if currentlyUsingGamepad then
resizeGamepadHintsFrame()
gamepadHintsFrame.Visible = true
end
enableGamepadInventoryControl()
else
gamepadHintsFrame.Visible = false
disableGamepadInventoryControl()
end
end
if InventoryFrame.Visible and GamepadEnabled then
ContextActionService:BindCoreAction("RBXRemoveSlot", removeHotBarSlot, false, Enum.KeyCode.ButtonX)
elseif GamepadEnabled then
ContextActionService:UnbindCoreAction("RBXRemoveSlot")
end
BackpackScript.IsOpen = InventoryFrame.Visible
BackpackScript.StateChanged:Fire(InventoryFrame.Visible)
local SettingsHub = require(RobloxGui.Modules:WaitForChild("SettingsHub"))
if SettingsHub.Instance.Visible then
SettingsHub:SetVisibility(false)
end
end
HotkeyFns[ARROW_HOTKEY] = openClose
BackpackScript.OpenClose = openClose -- Exposed
if not TopBarEnabled then
arrowFrame = NewGui('Frame', 'Arrow')
arrowFrame.BackgroundTransparency = BACKGROUND_FADE
arrowFrame.BackgroundColor3 = BACKGROUND_COLOR
arrowFrame.Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE / 2)
local hotbarBottom = HotbarFrame.Position.Y.Offset + HotbarFrame.Size.Y.Offset
arrowFrame.Position = UDim2.new(0.5, -arrowFrame.Size.X.Offset / 2, 1, hotbarBottom - arrowFrame.Size.Y.Offset)
arrowIcon = NewGui('ImageLabel', 'Icon')
arrowIcon.Image = ARROW_IMAGE_OPEN
arrowIcon.Size = ARROW_SIZE
arrowIcon.Position = UDim2.new(0.5, -arrowIcon.Size.X.Offset / 2, 0.5, -arrowIcon.Size.Y.Offset / 2)
arrowIcon.Parent = arrowFrame
collapsed = arrowFrame.Position
closed = collapsed + UDim2.new(0, 0, 0, -HotbarFrame.Size.Y.Offset)
opened = closed + UDim2.new(0, 0, 0, -InventoryFrame.Size.Y.Offset)
arrowFrame.Parent = MainFrame
end
-- Define global function
UpdateArrowFrame = function()
if arrowFrame then
arrowFrame.Position = (InventoryFrame.Visible) and opened or ((FullHotbarSlots == 0) and collapsed or closed)
end
end
end
-- Now that we're done building the GUI, we connect to all the major events
-- Wait for the player if LocalPlayer wasn't ready earlier
while not Player do
wait()
Player = PlayersService.LocalPlayer
end
-- Listen to current and all future characters of our player
Player.CharacterAdded:connect(OnCharacterAdded)
if Player.Character then
OnCharacterAdded(Player.Character)
end
do -- Hotkey stuff
-- Init HotkeyStrings, used for eating hotkeys
for i = 0, 9 do
table.insert(HotkeyStrings, tostring(i))
end
table.insert(HotkeyStrings, ARROW_HOTKEY_STRING)
-- Listen to key down
local con1 = UserInputService.InputBegan:connect(OnInputBegan)
-- Listen to ANY TextBox gaining or losing focus, for disabling all hotkeys
local con2 = UserInputService.TextBoxFocused:connect(function() TextBoxFocused = true end)
local con3 = UserInputService.TextBoxFocusReleased:connect(function() TextBoxFocused = false end)
-- Manual unequip for HopperBins on drop button pressed
HotkeyFns[DROP_HOTKEY_VALUE] = function() --NOTE: HopperBin
if ActiveHopper then
UnequipAllTools()
end
end
-- Listen to keyboard status, for showing/hiding hotkey labels
local con4 = UserInputService.Changed:connect(OnUISChanged)
OnUISChanged('KeyboardEnabled')
-- Listen to gamepad status, for allowing gamepad style selection/equip
if UserInputService:GetGamepadConnected(Enum.UserInputType.Gamepad1) then
gamepadConnected()
end
local con5 = UserInputService.GamepadConnected:connect(function(gamepadEnum)
if gamepadEnum == Enum.UserInputType.Gamepad1 then
gamepadConnected()
end
end)
local con6 = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if gamepadEnum == Enum.UserInputType.Gamepad1 then
gamepadDisconnected()
end
end)
UserSettings().GameSettings.Changed:connect(function(property)
if property == "VirtualVersion" then
con1:disconnect()
con2:disconnect()
con3:disconnect()
con4:disconnect()
con5:disconnect()
con6:disconnect()
end
end)
end
function BackpackScript:TopbarEnabledChanged(enabled)
topbarEnabled = enabled
-- Update coregui to reflect new topbar status
OnCoreGuiChanged(Enum.CoreGuiType.Backpack, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Backpack))
end
-- Listen to enable/disable signals from the StarterGui
StarterGui.CoreGuiChangedSignal:connect(OnCoreGuiChanged)
local backpackType, healthType = Enum.CoreGuiType.Backpack, Enum.CoreGuiType.Health
OnCoreGuiChanged(backpackType, StarterGui:GetCoreGuiEnabled(backpackType))
OnCoreGuiChanged(healthType, StarterGui:GetCoreGuiEnabled(healthType))
local TopbarPosition = MainFrame.Position
local GameSettings = UserSettings().GameSettings
--[[
GameSettings.Changed:connect(function(prop)
if prop ~= 'VirtualVersion' then return end
if GameSettings.VirtualVersion ~= Enum.VirtualVersion['2016'] then
MainFrame.Position = UDim2.new(0, 0, 0, HOTBAR_OFFSET_FROMBOTTOM)
else
MainFrame.Position = TopbarPosition
end
end)]]--
return BackpackScript