├── selene.toml ├── docs ├── CNAME ├── assets │ ├── store-card.png │ ├── creator-store.png │ ├── github-releases.png │ ├── attributes-example.png │ ├── sponsors │ │ ├── do-big-dark.png │ │ └── do-big-light.png │ ├── favicon.svg │ └── logo.svg ├── introduction.md ├── platforms.md ├── alternatives.md ├── usage.md ├── index.md ├── installation.md └── api-reference.md ├── models ├── SatchelLoader │ ├── Satchel │ │ ├── init.luau │ │ └── Packages.project.json │ └── init.client.luau └── ThumbnailCamera.model.json ├── assets ├── phone-thumbnail.png ├── xbox-thumbnail.png ├── computer-thumbnail.png ├── playstation-thumbnail.png ├── phone-inventory-thumbnail.png └── computer-inventory-thumbnail.png ├── default.project.json ├── package.project.json ├── rokit.toml ├── .vscode ├── extensions.json ├── settings.json └── tasks.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── documentation_issue.yml │ ├── feature_request.yml │ └── bug_report.yml ├── dependabot.yml ├── workflows │ ├── ci.yml │ ├── documentation.yml │ └── release.yml ├── SUPPORT.md └── CONTRIBUTING.md ├── .gitignore ├── develop.project.json ├── wally.lock ├── scripts ├── build.sh └── build.cmd ├── wally.toml ├── src ├── Attribution.luau ├── init.meta.json └── init.luau ├── .devcontainer └── devcontainer.json ├── mkdocs.yml ├── README.md └── LICENSE.md /selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | satchel.luau.page 2 | -------------------------------------------------------------------------------- /models/SatchelLoader/Satchel/init.luau: -------------------------------------------------------------------------------- 1 | return require(script.Packages["satchel"]) -------------------------------------------------------------------------------- /assets/phone-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/assets/phone-thumbnail.png -------------------------------------------------------------------------------- /assets/xbox-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/assets/xbox-thumbnail.png -------------------------------------------------------------------------------- /docs/assets/store-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/docs/assets/store-card.png -------------------------------------------------------------------------------- /assets/computer-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/assets/computer-thumbnail.png -------------------------------------------------------------------------------- /docs/assets/creator-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/docs/assets/creator-store.png -------------------------------------------------------------------------------- /docs/assets/github-releases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/docs/assets/github-releases.png -------------------------------------------------------------------------------- /assets/playstation-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/assets/playstation-thumbnail.png -------------------------------------------------------------------------------- /assets/phone-inventory-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/assets/phone-inventory-thumbnail.png -------------------------------------------------------------------------------- /docs/assets/attributes-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/docs/assets/attributes-example.png -------------------------------------------------------------------------------- /docs/assets/sponsors/do-big-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/docs/assets/sponsors/do-big-dark.png -------------------------------------------------------------------------------- /docs/assets/sponsors/do-big-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/docs/assets/sponsors/do-big-light.png -------------------------------------------------------------------------------- /assets/computer-inventory-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlua/satchel/HEAD/assets/computer-inventory-thumbnail.png -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Satchel", 3 | "emitLegacyScripts": false, 4 | "tree": { 5 | "$path": "src" 6 | } 7 | } -------------------------------------------------------------------------------- /package.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Satchel", 3 | "emitLegacyScripts": false, 4 | "tree": { 5 | "$path": "models" 6 | } 7 | } -------------------------------------------------------------------------------- /rokit.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | rojo = "rojo-rbx/rojo@7.6.1" 3 | selene = "Kampfkarren/selene@0.29.0" 4 | stylua = "JohnnyMorganz/stylua@2.3.1" 5 | wally = "UpliftGames/wally@0.3.2" 6 | -------------------------------------------------------------------------------- /models/SatchelLoader/Satchel/Packages.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Packages", 3 | "tree": { 4 | "$path": "../../../Packages", 5 | "satchel": { 6 | "$path": "../../../src" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "evaera.vscode-rojo", 4 | "kampfkarren.selene-vscode", 5 | "johnnymorganz.stylua", 6 | "johnnymorganz.luau-lsp", 7 | "redhat.vscode-yaml" 8 | ] 9 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Ask on our Discord server 4 | url: https://discord.gg/N2KEnHzrsW 5 | about: Ask members in dedicated channel on our Discord 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rojo 2 | sourcemap.json 3 | *.rbxl 4 | *.rbxlx 5 | *.rbxm 6 | *.rbxmx 7 | 8 | # Wally 9 | Packages/ 10 | DevPackages/ 11 | 12 | # MkDocs documentation 13 | site/ 14 | 15 | # Workflow builds 16 | builds/ 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "devcontainers" 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /develop.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Develop Satchel", 3 | "emitLegacyScripts": false, 4 | "tree": { 5 | "$className": "DataModel", 6 | "ReplicatedStorage": { 7 | "$className": "ReplicatedStorage", 8 | "SatchelLoader": { 9 | "$path": "models/SatchelLoader" 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_issue.yml: -------------------------------------------------------------------------------- 1 | name: Help improve documentation 2 | description: Report issues in our documentation 3 | labels: ["documentation"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Provide a description of requested docs changes 8 | placeholder: Briefly describe which document needs to be corrected and why. 9 | -------------------------------------------------------------------------------- /wally.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Wally. 2 | # It is not intended for manual editing. 3 | registry = "test" 4 | 5 | [[package]] 6 | name = "1foreverhd/topbarplus" 7 | version = "3.4.0" 8 | dependencies = [] 9 | 10 | [[package]] 11 | name = "ryanlua/satchel" 12 | version = "1.4.1" 13 | dependencies = [["topbarplus", "1foreverhd/topbarplus@3.4.0"]] 14 | -------------------------------------------------------------------------------- /models/SatchelLoader/init.client.luau: -------------------------------------------------------------------------------- 1 | --[[ 2 | 💖 Thanks for using Satchel 💖 3 | 4 | Satchel is a modern open-source alternative to Roblox's default backpack 🎒 5 | 6 | 📰 DevForum: https://devforum.roblox.com/t/2451549 7 | 🛍️ Creator Store: https://create.roblox.com/store/asset/13947506401 8 | 🛝 Playground: https://www.roblox.com/games/13592168150 9 | ]] 10 | 11 | require(script.Satchel) 12 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Building Rojo projects..." 5 | 6 | BUILD_DIR="builds" 7 | OUTPUT_NAME="Satchel" 8 | BUILD_PROJECT="package.project.json" 9 | 10 | # Setup build directory 11 | echo "Cleaning up build directory..." 12 | rm -rf "$BUILD_DIR" 13 | mkdir -p "$BUILD_DIR" 14 | 15 | # Build .rbxm file 16 | rojo build --output "$BUILD_DIR/$OUTPUT_NAME.rbxm" $BUILD_PROJECT 17 | 18 | # Build .rbxmx file 19 | rojo build --output "$BUILD_DIR/$OUTPUT_NAME.rbxmx" $BUILD_PROJECT 20 | 21 | echo "Build completed successfully!" -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://squidfunk.github.io/mkdocs-material/schema.json": "mkdocs.yml" 4 | }, 5 | "yaml.customTags": [ 6 | "!ENV scalar", 7 | "!ENV sequence", 8 | "!relative scalar", 9 | "tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg", 10 | "tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji", 11 | "tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format", 12 | "tag:yaml.org,2002:python/object/apply:pymdownx.slugs.slugify mapping" 13 | ] 14 | } -------------------------------------------------------------------------------- /wally.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ryanlua/satchel" 3 | description = "A modern open-source alternative to Roblox's default backpack." 4 | version = "1.4.1" 5 | license = "MPL-2.0" 6 | authors = ["Ryan Luu "] 7 | realm = "shared" 8 | registry = "https://github.com/UpliftGames/wally-index" 9 | homepage = "http://satchel.luau.page/" 10 | repository = "https://github.com/ryanlua/satchel" 11 | exclude = ["**"] 12 | include = ["src", "src/**", "wally.toml", "wally.lock", "default.project.json", "LICENSE", "README.md"] 13 | 14 | [dependencies] 15 | topbarplus = "1foreverhd/topbarplus@3.4.0" 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build Rojo project", 8 | "type": "shell", 9 | "command": "./scripts/build.sh", 10 | "windows": { 11 | "command": ".\\scripts\\build.cmd" 12 | }, 13 | "problemMatcher": [], 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /scripts/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | echo Building Rojo projects... 5 | 6 | set "BUILD_DIR=builds" 7 | set "OUTPUT_NAME=Satchel" 8 | set "BUILD_PROJECT=package.project.json" 9 | 10 | REM Setup build directory 11 | echo Cleaning up build directory... 12 | if exist "%BUILD_DIR%" rmdir /s /q "%BUILD_DIR%" 13 | mkdir "%BUILD_DIR%" 14 | cd "%BUILD_DIR%" 15 | 16 | REM Build .rbxm file 17 | rojo build --output "%BUILD_DIR%\%OUTPUT_NAME%.rbxm" ..\%BUILD_PROJECT% 18 | 19 | REM Build .rbxmx file 20 | rojo build --output "%BUILD_DIR%\%OUTPUT_NAME%.rbxmx" ..\%BUILD_PROJECT% 21 | 22 | echo Build completed successfully! 23 | cd .. -------------------------------------------------------------------------------- /models/ThumbnailCamera.model.json: -------------------------------------------------------------------------------- 1 | { 2 | "ClassName": "Camera", 3 | "Properties": { 4 | "CFrame": [ 5 | 0, 3, 0, 6 | -1, 1.50995803e-07, 6.60023616e-15, 7 | 0, -4.37113883e-08, 1, 8 | 1.50995803e-07, 1, 4.37113883e-08 9 | ] 10 | }, 11 | "Children": [ 12 | { 13 | "ClassName": "Part", 14 | "Properties": { 15 | "Size": [4, 1, 4] 16 | }, 17 | "Children": [ 18 | { 19 | "ClassName": "Decal", 20 | "Properties": { 21 | "Face": "Top", 22 | "Texture": "rbxassetid://18139994576" 23 | } 24 | } 25 | ] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /src/Attribution.luau: -------------------------------------------------------------------------------- 1 | --[[ 2 | Satchel is subject to the terms of the Mozilla Public License, v. 2.0. 3 | 4 | This script serves as the license notice for Satchel to meet MPL requirements. 5 | 6 | By using Satchel, you are required to do one of the following: 7 | 1. Keep the Attribution script unmodified and enabled 8 | 2. Credit Satchel in your experience description by name and creator (@WinnersTakesAll) 9 | 3. Have a DevForum topic linked in your experience description, crediting Satchel with the above 10 | 11 | Thank you for supporting Satchel. For more, consider sponsoring Satchel to support its development. 12 | ]] 13 | 14 | local RunService = game:GetService("RunService") 15 | 16 | if not RunService:IsStudio() then 17 | print("💼 Running Satchel v1.4.1 by @WinnersTakesAll") 18 | end 19 | 20 | return {} 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/ci.yml" 7 | - "**.luau" 8 | push: 9 | paths: 10 | - ".github/workflows/ci.yml" 11 | - "**.luau" 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | lint: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v6 21 | 22 | - name: Setup Selene 23 | uses: CompeyDev/setup-rokit@v0.1.2 24 | 25 | - name: Run Selene 26 | run: selene src 27 | 28 | style: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v6 32 | 33 | - name: Run StyLua 34 | uses: JohnnyMorganz/stylua-action@v4 35 | with: 36 | token: ${{ secrets.GITHUB_TOKEN }} 37 | version: latest 38 | args: --check src 39 | -------------------------------------------------------------------------------- /docs/assets/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/init.meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "Attributes": { 4 | "BackgroundColor3": { 5 | "Color3": [0.0980392157, 0.105882353, 0.11372549] 6 | }, 7 | "BackgroundTransparency": { 8 | "Float32": 0.3 9 | }, 10 | "CornerRadius": { 11 | "UDim": [0, 8] 12 | }, 13 | "EquipBorderColor3": { 14 | "Color3": [1, 1, 1] 15 | }, 16 | "EquipBorderSizePixel": { 17 | "Float32": 5 18 | }, 19 | "InsetIconPadding": { 20 | "Bool": true 21 | }, 22 | "OutlineEquipBorder": { 23 | "Bool": true 24 | }, 25 | "TextColor3": { 26 | "Color3": [1, 1, 1] 27 | }, 28 | "TextSize": { 29 | "Float32": 16 30 | }, 31 | "TextStrokeColor3": { 32 | "Color3": [0, 0, 0] 33 | }, 34 | "TextStrokeTransparency": { 35 | "Float32": 0.5 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Suggest a new feature 2 | description: Suggest an idea for this project 3 | labels: ["enhancement"] 4 | body: 5 | - type: textarea 6 | id: problem-related 7 | attributes: 8 | label: Is your feature request related to a problem? 9 | description: If so, please provide a clear and concise description of what the problem is. 10 | placeholder: What is the problem? 11 | - type: textarea 12 | id: solution 13 | attributes: 14 | label: Describe the solution you'd like 15 | description: A clear and concise description of what you want to happen. 16 | placeholder: What do you want to happen? 17 | - type: textarea 18 | id: alternatives 19 | attributes: 20 | label: Describe alternatives you've considered 21 | description: A clear and concise description of any alternative solutions or features you've considered. 22 | placeholder: What have you considered? 23 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - ".github/workflows/documentation.yml" 9 | - "docs/**" 10 | - "mkdocs.yml" 11 | 12 | env: 13 | VERSION: 1.x 14 | 15 | permissions: 16 | contents: write 17 | 18 | jobs: 19 | deploy: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v6 23 | with: 24 | fetch-depth: 0 25 | sparse-checkout: | 26 | docs 27 | 28 | - name: Configure Git Credentials 29 | run: | 30 | git config user.name github-actions[bot] 31 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 32 | 33 | - uses: actions/setup-python@v6 34 | with: 35 | python-version: 3.x 36 | 37 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 38 | 39 | - uses: actions/cache@v5 40 | with: 41 | key: mkdocs-material-${{ env.cache_id }} 42 | path: ~/.cache 43 | restore-keys: | 44 | mkdocs-material- 45 | 46 | - run: pip install mkdocs-material mike 47 | 48 | - name: Fetch gh-pages branch 49 | run: git fetch origin gh-pages --depth=1 50 | 51 | - name: Deploy latest documentation 52 | run: mike deploy $VERSION --branch gh-pages --push 53 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | Welcome to Satchel, a modern alternative to Roblox's default backpack. 2 | 3 | Satchel and its documentation are always a work in progress, but you can help too. See the [contributing guidelines](https://github.com/ryanlua/satchel/blob/main/.github/CONTRIBUTING.md) to find how you can improve Satchel. 4 | 5 | Just want to use Satchel? Check out [Installation]. 6 | 7 | [Installation]: installation.md 8 | 9 | ## Improvements 10 | 11 | * Modernized and refreshed UI 12 | * Customization using instance attributes 13 | * Methods and events, previously locked to CoreGui 14 | * Script readability and type improvements 15 | * Rojo sync and Wally support 16 | 17 | All open source and free for you to use in your own Roblox experiences. 18 | 19 | ## Satchel over Default 20 | 21 | While the default backpack does its job, customizing the UI or editing the script is extremely difficult. Did you know that the backpack they are using today is from 2015? (With lots of bandaids and patches of course.) Satchel acts as a modernized version that aims to be much more friendly while still maintaining as many features and compatibility. 22 | 23 | ## CoreGui Relation 24 | 25 | From a scripting perspective, Satchel is more of an advanced fork of the CoreGui with Satchel borrowing a majority of its codebase from the default. It's not entirely copy and paste job though. Type annotations and performance optimizations set Satchel apart along with its number of UI tweaks and refactors in place. 26 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | For support please ensure you are using the latest version. 4 | 5 | ## Supported Platforms 6 | 7 | We support the platforms listed below. We cannot provide support for platforms not listed here. Any platforms not mentioned are not supported. 8 | 9 | If you would like to see support for another platform or report a platform-specific bug, please refer to [reporting a bug](#reporting-a-bug) and [requesting a feature](#requesting-a-feature). 10 | 11 | - [x] Computers 12 | - [x] Tablets 13 | - [x] Phones 14 | - [x] Consoles 15 | - [x] VR 16 | 17 | ## GitHub Issues 18 | 19 | ### Reporting a Bug 20 | 21 | If you've discovered a bug, please [file a bug report](https://github.com/ryanlua/satchel/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml). When creating the issue, please provide the following information: 22 | 23 | - A clear and concise description of the bug. 24 | - Steps to reproduce the behavior. 25 | - Expected and actual results. 26 | - Screenshots, if applicable. 27 | - Any other important information. 28 | 29 | ### Requesting a Feature 30 | 31 | If you have a suggestion for a new feature or an improvement, please [open a feature request](https://github.com/ryanlua/satchel/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml). When creating the issue, please provide the following information: 32 | 33 | - A clear and concise description of the feature. 34 | - Explanation of why this feature would be beneficial. 35 | - Any potential alternatives you've considered. 36 | -------------------------------------------------------------------------------- /docs/platforms.md: -------------------------------------------------------------------------------- 1 | We support all platforms that Roblox supports. Computers, phones, tablets, consoles, and VR are all supported by Satchel right out of the box. Where the default backpack should run, so should Satchel. 2 | 3 | !!! note 4 | 5 | Do you see a bug specific to a platform? [Open a bug report] we'll look into it. 6 | 7 | [Open a bug report]: https://github.com/ryanlua/satchel/issues/new 8 | 9 | ## Current supported devices 10 | 11 | All platforms on Roblox are supported by Satchel, limited only by screen size. Below is a list of devices along with the accompanying interface and minimum screen size. 12 | 13 | ### Computer 14 | 15 | Support for all computers with 1024 x 768px or larger. 16 | 17 | * 1024 x 768px minimum display size 18 | * Desktop interface 19 | * 10 hotbar slots 20 | 21 | ### Phone 22 | 23 | Support for Apple iPhone 5 (568 x 320px) or newer. 24 | 25 | * 568 x 320px minimum display size 26 | * Mobile interface 27 | * 6 hotbar slots 28 | 29 | ### Tablet 30 | 31 | Support for Apple iPad 2 (1024 x 768px) or newer. 32 | 33 | * 1024 x 768px minimum display size 34 | * Mobile interface 35 | * 10 or 6 hotbar slots (Depending on display size) 36 | 37 | ### Console 38 | 39 | Support for Xbox and PlayStation. Specialized ten-foot interface and hint UI for controllers. Hint UI will automatically adapt to the correct controller buttons. 40 | 41 | * Ten-foot interface 42 | * Controller context hint UI 43 | * 10 hotbar slots 44 | 45 | ### VR 46 | 47 | VR including Valve Index, Meta Quest 2 and above, and similar. 48 | 49 | * Adapted mobile interface 50 | * Controller context hint UI 51 | * Custom VR inventory controls 52 | * 6 hotbar slots 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Report a bug 2 | description: Something is not working correctly 3 | labels: ["bug"] 4 | body: 5 | - type: input 6 | id: version 7 | attributes: 8 | label: Satchel version 9 | description: What version does your bug occur on? 10 | placeholder: 1.0.0 11 | - type: textarea 12 | id: description 13 | attributes: 14 | label: Description 15 | description: A clear and concise description of what the bug is. 16 | placeholder: Write a brief sentence about the bug 17 | - type: textarea 18 | id: expected-behavior 19 | attributes: 20 | label: Expected behavior 21 | description: What did you expect to happen? 22 | placeholder: When I do this, that is supposed to happen 23 | - type: textarea 24 | id: actual-behavior 25 | attributes: 26 | label: Actual behavior 27 | description: What actually happened instead? If you have a screenshot or video, attach them. 28 | placeholder: When I do this, it instead does that 29 | - type: textarea 30 | id: repro-steps 31 | attributes: 32 | label: Steps to reproduce 33 | description: How can we reproduce the issue? 34 | placeholder: | 35 | 1. Go to '...' 36 | 2. Click on '...' 37 | 3. Scroll down to '...' 38 | 4. See error 39 | - type: textarea 40 | id: logs 41 | attributes: 42 | label: Relevant log output 43 | description: Any logs from the [Output window](https://create.roblox.com/docs/studio/output) or [Developer Console log](https://create.roblox.com/docs/studio/developer-console#log). 44 | placeholder: 12:00:00.000 Hello world! - Client - Satchel:1 45 | render: lua 46 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/ryanlua/templates/tree/main/src/roblox 3 | { 4 | "name": "Roblox", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfil 6 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu", 7 | 8 | "features": { 9 | "ghcr.io/ryanlua/features/rojo:0.2.1": { 10 | "version": "latest", 11 | "toolchainManager": "rokit" 12 | }, 13 | "ghcr.io/devcontainers/features/python:1": {} 14 | }, 15 | 16 | // Configure tool-specific properties. 17 | "customizations": { 18 | // Configure properties specific to VS Code. 19 | "codespaces": { 20 | "openFiles": [ 21 | "src/init.luau" 22 | ] 23 | }, 24 | "vscode": { 25 | // Add the IDs of extensions you want installed when the container is created. 26 | "extensions": [ 27 | "kampfkarren.selene-vscode", 28 | "johnnymorganz.stylua", 29 | "johnnymorganz.luau-lsp", 30 | "DavidAnson.vscode-markdownlint" 31 | ] 32 | } 33 | }, 34 | 35 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 36 | "forwardPorts": [ 37 | 8000, 38 | 34872 39 | ], 40 | "portsAttributes": { 41 | "8000": { 42 | "label": "MkDocs", 43 | "onAutoForward": "openPreview" 44 | }, 45 | "34872": { 46 | "label": "Rojo" 47 | } 48 | }, 49 | 50 | // Use 'postCreateCommand' to run commands after the container is created. 51 | "postCreateCommand": "pip install mkdocs-material mike --break-system-packages && rokit install --no-trust-check && wally install", 52 | 53 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 54 | "remoteUser": "root" 55 | } -------------------------------------------------------------------------------- /docs/alternatives.md: -------------------------------------------------------------------------------- 1 | Satchel isn't the best backpack system out there or the only one but it does offer some advantages against others. 2 | 3 | ## Purse 4 | 5 | [Purse] is a sub project of Satchel and a fork on the CoreGui backpack. It tries to be as close as possible to the default backpack while Satchel tries to be an improvement over it. 6 | 7 | [Purse]: https://purse.luau.page/ 8 | 9 | ### Pros 10 | 11 | - Easy to install, drag and drop installation 12 | - Well documented 13 | - Full platform support 14 | 15 | ### Cons 16 | 17 | - Requires scripting knowledge to customize 18 | - No new features over default backpack 19 | 20 | ## NeoHotbar 21 | 22 | [Neobar] is a modern hotbar-only system that acts as a great alternative to Satchel if you are looking for a hotbar only. Made on a strong foundation and well-built, [Neobar] is a powerful tool with unparalleled customization and API. 23 | 24 | [Neobar]: https://loneka.com/neohotbar/ 25 | 26 | ### Pros 27 | 28 | - Easy to install, drag and drop installation 29 | - Powerful and highly customizable interface 30 | - Well documented 31 | - Full platform support 32 | 33 | ### Cons 34 | 35 | - Requires scripting knowledge to customize 36 | - No instance attributes 37 | - Hotbar system only 38 | 39 | ## ReInvent 40 | 41 | [ReInvent] is an older-style hotbar and inventory system that is made completely separate from the backpack core scripts. [ReInvent] is no longer supported and lacks proper documentation and developer-facing APIs. 42 | 43 | [ReInvent]: https://devforum.roblox.com/t/1822656 44 | 45 | ### Pros 46 | 47 | - Easy to install, drag and drop installation 48 | - Animated interface 49 | - Backpack and hotbar system 50 | 51 | ### Cons 52 | 53 | - Computer and mobile platforms only 54 | - Poor documentation 55 | - Discontinued 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag_name: 7 | description: 'Tag name' 8 | required: true 9 | type: string 10 | 11 | env: 12 | OUTPUT_NAME: Satchel 13 | BUILD_PROJECT: package.project.json 14 | 15 | permissions: 16 | contents: write 17 | discussions: write 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v6 24 | 25 | - name: Setup Rokit 26 | uses: CompeyDev/setup-rokit@v0.1.2 27 | 28 | - name: Setup Wally 29 | run: wally install 30 | 31 | - name: Build using Rojo 32 | run: | 33 | mkdir builds 34 | rojo build --output builds/$OUTPUT_NAME.rbxm $BUILD_PROJECT 35 | rojo build --output builds/$OUTPUT_NAME.rbxmx $BUILD_PROJECT 36 | 37 | - name: Archive build files 38 | uses: actions/upload-artifact@v6 39 | with: 40 | name: release-file-builds 41 | path: builds/ 42 | if-no-files-found: error 43 | 44 | release: 45 | needs: build 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v6 49 | 50 | - name: Setup Rokit 51 | uses: CompeyDev/setup-rokit@v0.1.2 52 | 53 | - name: Setup Wally 54 | env: 55 | WALLY_AUTH_TOKEN: ${{ secrets.WALLY_AUTH_TOKEN }} 56 | run: | 57 | wally login --token "$WALLY_AUTH_TOKEN" 58 | wally install 59 | 60 | - name: Download build files 61 | uses: actions/download-artifact@v7 62 | with: 63 | name: release-file-builds 64 | path: builds/ 65 | 66 | - name: Create Release 67 | uses: softprops/action-gh-release@v2 68 | with: 69 | token: ${{ secrets.GITHUB_TOKEN }} 70 | files: builds/* 71 | generate_release_notes: true 72 | tag_name: ${{ inputs.tag_name }} 73 | draft: true 74 | 75 | - name: Publish to Wally 76 | run: wally publish 77 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | Use of Satchel after installation very easy. Just [publish your experience to Roblox] and see Satchel live in action. 2 | 3 | To learn how to install Satchel, see [Installation]. 4 | 5 | !!! note 6 | 7 | Please see [API Reference] for more details on attributes, methods, and events for Satchel and how to use Satchel to it's full potential. 8 | 9 | [publish your experience to Roblox]: https://create.roblox.com/docs/production/publishing 10 | [Installation]: installation.md 11 | [API Reference]: api-reference.md 12 | 13 | ### Customization 14 | 15 | Satchel is highly customizable & adjustable with [instance attributes] support allowing you to customize the behavior and appearance of over 10+ attributes. 16 | 17 | Some of the attributes include: 18 | 19 | * Text Color, Size, Stroke Color & Transparency 20 | * Background Color & Transparency 21 | * Equip Border Color & Thickness 22 | * Corner Radius 23 | * Font 24 | 25 | More attributes can be found in the [API Reference]. The list above is not exhaustive and there are may more attributes available for customization. 26 | 27 | [instance attributes]: https://create.roblox.com/docs/studio/instance-attributes 28 | 29 |
30 | ![Instance Attributes](assets/attributes-example.png) 31 |
Example of customization using instance attributes
32 |
33 | 34 | ### Scripting 35 | 36 | Satchel offers methods and events for scripting purposes. In the below code example we will use the `SetBackpackEnabled` method to disable the Satchel. The script expects the Satchel module to be in [`ReplicatedStorage`][ReplicatedStorage]. 37 | 38 | ``` lua title="Disable Backpack" 39 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 40 | 41 | local Satchel = require(ReplicatedStorage.Satchel) 42 | 43 | Satchel.SetBackpackEnabled(false) 44 | ``` 45 | 46 | For the full API reference, see [API Reference] for more details on attributes, methods, and events for Satchel and how to use Satchel to it's full potential. 47 | 48 | [ReplicatedStorage]: https://create.roblox.com/docs/reference/engine/classes/ReplicatedStorage 49 | [SetBackpackEnabled]: api-reference.md#setbackpackenabled 50 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Satchel Documentation 2 | site_url: https://satchel.luau.page/ 3 | repo_name: ryanlua/satchel 4 | repo_url: https://github.com/ryanlua/satchel 5 | 6 | copyright: Copyright © Ryan Luu 7 | 8 | theme: 9 | name: material 10 | features: 11 | - content.code.copy 12 | - content.tooltips 13 | - navigation.footer 14 | - navigation.top 15 | palette: 16 | - media: "(prefers-color-scheme)" 17 | toggle: 18 | icon: material/brightness-auto 19 | name: Switch to light mode 20 | - media: "(prefers-color-scheme: light)" 21 | scheme: default 22 | toggle: 23 | icon: material/brightness-7 24 | name: Switch to dark mode 25 | - media: "(prefers-color-scheme: dark)" 26 | scheme: slate 27 | primary: black 28 | toggle: 29 | icon: material/brightness-4 30 | name: Switch to system preference 31 | icon: 32 | repo: fontawesome/brands/github 33 | favicon: assets/favicon.svg 34 | logo: assets/logo.svg 35 | 36 | extra: 37 | version: 38 | provider: mike 39 | social: 40 | - icon: simple/github 41 | link: https://github.com/ryanlua/satchel 42 | name: GitHub 43 | - icon: simple/roblox 44 | link: https://create.roblox.com/store/asset/13947506401 45 | name: Creator Store 46 | - icon: simple/robloxstudio 47 | link: https://devforum.roblox.com/t/2451549 48 | name: DevForum 49 | - icon: simple/discord 50 | link: https://discord.gg/N2KEnHzrsW 51 | name: Discord 52 | 53 | markdown_extensions: 54 | - abbr 55 | - admonition 56 | - attr_list 57 | - md_in_html 58 | - pymdownx.details 59 | - pymdownx.highlight 60 | - pymdownx.inlinehilite 61 | - pymdownx.superfences 62 | - toc: 63 | permalink: true 64 | toc_depth: 3 65 | - pymdownx.highlight: 66 | linenums: true 67 | - pymdownx.emoji: 68 | emoji_index: !!python/name:material.extensions.emoji.twemoji 69 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 70 | 71 | nav: 72 | - Home: index.md 73 | - Introduction: introduction.md 74 | - Installation: installation.md 75 | - Usage: usage.md 76 | - Platforms: platforms.md 77 | - Alternatives: alternatives.md 78 | - API Reference: api-reference.md 79 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for investing your time in contributing to our project! 4 | 5 | Please read our [Code of Conduct](https://github.com/ryanlua/satchel?tab=coc-ov-file) to keep our community approachable and respectable. 6 | 7 | When you contribute, you become a project contributor and both are shown on [our repository](https://github.com/ryanlua/satchel), [contributors graph](https://github.com/ryanlua/satchel/graphs/contributors), and [your contribution activity](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/viewing-contributions-on-your-profile#contribution-activity). 8 | 9 | ## GitHub Codespaces 10 | 11 | We are fully integrated with [GitHub Codespaces](https://docs.github.com/en/codespaces). When you create a codespace on our repository, all required extensions and dependencies are automatically installed to make contributing easier. The following will be automatically available: 12 | 13 | * [Rojo](https://github.com/rojo-rbx/rojo) sync (If editing in Visual Studio Code from your local desktop client. Requires [Visual Studio Code](https://code.visualstudio.com/download/) with the [GitHub Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces) extension) 14 | * [Rojo](https://github.com/rojo-rbx/rojo), [Selene](https://github.com/Kampfkarren/selene), and [StyLua](https://github.com/JohnnyMorganz/StyLua) Extensions 15 | * [Wally](https://github.com/UpliftGames/wally/) packages 16 | 17 | Additionally, our codespaces are preconfigured with [pre-builds](https://docs.github.com/en/codespaces/prebuilding-your-codespaces/about-github-codespaces-prebuilds) and [dev containers](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers). 18 | 19 | ## Coding Style 20 | 21 | If you plan to contribute, ensure that all code written in Lua follows the [Roblox Lua Style Guide](https://roblox.github.io/lua-style-guide/) when applicable. 22 | 23 | Also, make sure that your code is properly styled and linted using [Selene](https://github.com/Kampfkarren/selene) and [StyLua](https://github.com/JohnnyMorganz/StyLua). 24 | 25 | ## Pull Requests 26 | 27 | We actively welcome your pull requests. When you submit a pull request, we will review your code and respond as soon as possible. We may suggest changes, improvements, or alternatives. 28 | 29 | ## License 30 | 31 | By contributing, you agree that your contributions will be licensed under the [Mozilla Public License 2.0](http://mozilla.org/MPL/2.0/). 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 | Satchel 5 |

6 | 7 |
8 | 9 | [![CI](https://github.com/ryanlua/satchel/actions/workflows/ci.yml/badge.svg)](https://github.com/ryanlua/satchel/actions/workflows/ci.yml) 10 | [![GitHub Release](https://img.shields.io/github/v/release/ryanlua/satchel)](https://github.com/ryanlua/satchel/releases) 11 | [![Docs](https://img.shields.io/badge/docs-website-green)](https://satchel.luau.page/) 12 | [![Playground](https://img.shields.io/badge/playground-experience-blue)](https://www.roblox.com/games/13592168150) 13 | [![Discord](https://discord.com/api/guilds/1162303282002272359/widget.png)](https://discord.gg/N2KEnHzrsW) 14 | [![Mentioned in Awesome Roblox](https://awesome.re/mentioned-badge.svg)](https://github.com/awesome-roblox/awesome-roblox) 15 | 16 |
17 | 18 | > [!TIP] 19 | > Want just the CoreGui backpack? Check out [Purse](https://github.com/ryanlua/purse), a sub-project of Satchel. 20 | 21 | Satchel is a modern open-source alternative to Roblox's default backpack. Satchel aims to be more customizable and easier to use than the default backpack while still having a "vanilla" feel. Installation of Satchel is as simple as dropping the module into your game and setting up a few properties if you like to customize it. It has a familiar feel and structure as to the default backpack for ease of use for both developers and players. 22 | 23 | Satchel on computer Satchel on computer with inventory open 24 | Satchel on mobile Satchel on mobile with inventory open 25 | 26 | 27 | 28 | ## Documentation 29 | 30 | See the [documentation site](https://satchel.luau.page) for more about Satchel. Find guides on how to get started, learn about the API, understand what Satchel is, and more. 31 | 32 | If you see anything wrong, open a new [documentation issue](https://github.com/ryanlua/satchel/issues/new?template=documentation_issue.yml). 33 | 34 | ## Sponsors 35 | 36 | Special thanks for our sponsors for supporting Satchel and it's future development. We distribute Satchel and provide updates for free, for anyone to use or modify. 37 | 38 |
39 | 40 |

41 | 42 | 43 | 44 | 45 | Do Big Studios 46 | 47 | 48 |

49 | 50 |
51 | 52 | [Become a sponsor](https://github.com/sponsors/ryanlua) 53 | 54 | ## Contributing 55 | 56 | We welcome all contributions from the community. See the [contributing guidelines](.github/CONTRIBUTING.md) for details. 57 | 58 | ## License 59 | 60 | Satchel is available under the Mozilla Public License 2.0 license. See [LICENSE.md](LICENSE.md) for details. 61 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | Satchel is a modern open-source alternative to Roblox's default backpack. 14 | 15 | Satchel aims to be more customizable and easier to use than the default backpack while still having a "vanilla" feel. Installation of Satchel is as simple as dropping the module into your game and setting up a few properties if you like to customize it. It has a familiar feel and structure as to the default backpack for ease of use for both developers and players. 16 | 17 | This documentation will allow you to install Satchel and learn about how to script using Satchel. 18 | 19 | 30 | 31 |
32 | 33 |
34 | 35 | --- 36 | 37 |
38 | 39 | - :material-clock-fast:{ .lg .middle } __Fast and easy installation__ 40 | 41 | --- 42 | 43 | Drag and drop installation from the [Creator Store] or [GitHub Releases] 44 | 45 | [:material-arrow-right: Installation](installation.md) 46 | 47 | [Creator Store]: https://create.roblox.com/store/asset/13947506401 48 | [GitHub Releases]: https://github.com/ryanlua/satchel/releases 49 | 50 | - :material-devices:{ .lg .middle } __Full device support__ 51 | 52 | --- 53 | 54 | Compatible with computer, phone, tablet, console, and VR 55 | 56 | [:material-arrow-right: Platforms](platforms.md) 57 | 58 | - :material-toolbox-outline:{ .lg .middle } __Highly customizable__ 59 | 60 | --- 61 | 62 | Change colors, fonts, and more using [instance attributes] 63 | 64 | [:material-arrow-right: Customization](usage.md#customization) 65 | 66 | [instance attributes]: https://create.roblox.com/docs/studio/instance-attributes 67 | 68 | - :material-scale-balance:{ .lg .middle } __Free and open-source__ 69 | 70 | --- 71 | 72 | Open source for everyone to use and available on [GitHub] 73 | 74 | [:material-arrow-right: License](https://github.com/ryanlua/satchel#MPL-2.0-1-ov-file) 75 | 76 | [GitHub]: https://github.com/ryanlua/satchel 77 | 78 |
79 | 80 | 81 | 82 | --- 83 | 84 | ## Our Sponsors 85 | 86 | Special thanks for our sponsors for supporting Satchel and it's future development. We distribute Satchel and provide updates for free, for anyone to use or modify. 87 | 88 |
89 | 90 | [![Do Big Studios](assets/sponsors/do-big-light.png#only-light){ style="height: 60px" }](https://www.dobigstudios.com/){ target=_blank title="Do Big Studios" } 91 | [![Do Big Studios](assets/sponsors/do-big-dark.png#only-dark){ style="height: 60px" }](https://www.dobigstudios.com/){ target=_blank title="Do Big Studios" } 92 | 93 |
94 | 95 | [:material-heart: Sponsor Satchel][sponsor]{ .md-button .md-button--primary } 96 | 97 | [sponsor]: https://github.com/sponsors/ryanlua "Become a sponsor" 98 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | Installing Satchel is easy and painless. Satchel is a drag-and-drop module that works out of the box and with no configuration needed. 2 | 3 | Satchel uses [RunContext] to run anywhere, so you do not need to move it from [Workspace], though it is recommeneded to parent to [`ReplicatedStorage`][ReplicatedStorage] for best practices and organizational reasons. 4 | 5 | !!! danger 6 | 7 | Do not parent the Satchel to any starter containers or it will run multiple times. Older versions of Satchel required you to do this but it is no longer needed in versions [`v1.4.0`][v1.4.0] and newer. 8 | 9 | [RunContext]: https://devforum.roblox.com/t/1938784 10 | [Workspace]: https://create.roblox.com/docs/reference/engine/classes/Workspace 11 | [ReplicatedStorage]: https://create.roblox.com/docs/reference/engine/classes/ReplicatedStorage 12 | [v1.4.0]: https://github.com/ryanlua/satchel/releases/tag/v1.4.0 13 | 14 | ## Creator Store recommended { #creator-store data-toc-label="Creator Store" } 15 | 16 | The Creator Store is the easiest way to install Satchel. It is a one-click installation and requires no downloads. 17 | 18 | ???+ example 19 | 20 | Below is a video on how to install Satchel from the Creator Store. 21 | 22 |
23 | 24 |
25 | 26 | 1. Get the **Satchel** model from the [Creator Store]. 27 | 28 | ![Creator Store](assets/creator-store.png){ width="100%" } 29 | 30 | 1. Open Roblox Studio and create a new place or open an existing place. 31 | 32 | 1. From Studio's Window menu or Home tab toolbar, open the [Toolbox] and select the **Inventory** tab. 33 | 34 | ![Toolbox](https://prod.docsiteassets.roblox.com/assets/studio/general/Toolbar-Toolbox.png) 35 | 36 | ![Inventory Tab](https://prod.docsiteassets.roblox.com/assets/studio/toolbox/Inventory-Tab.png) 37 | 38 | 1. Locate the **Satchel** model and click it, or drag-and-drop it into the 3D view. 39 | 40 | ![Toolbox](assets/store-card.png) 41 | 42 | 1. In the [Explorer] window, move the **Satchel** model into [`ReplicatedStorage`][ReplicatedStorage]. 43 | 44 | [Creator Store]: https://create.roblox.com/store/asset/13947506401 45 | [Toolbox]: https://create.roblox.com/docs/projects/assets/toolbox 46 | [Explorer]: https://create.roblox.com/docs/studio/explorer 47 | 48 | ## GitHub Releases 49 | 50 | 1. Download the `Satchel.rbxm` or `Satchel.rbxmx` model file from [GitHub Releases]. 51 | 52 | !!! info 53 | 54 | Binary (`.rbxm`) and XML (`.rbxmx`) model files contain the exact same model. `.rbxm` is a smaller file size to download. 55 | 56 | ![GitHub Release](assets/github-releases.png) 57 | 58 | 1. Open Roblox Studio and create a new place or open an existing place. 59 | 60 | 1. In the [Explorer] window, insert **Satchel** into [`ReplicatedStorage`][ReplicatedStorage]. 61 | 62 | ![Contextual menu](https://prod.docsiteassets.roblox.com/assets/studio/explorer/Context-Menu-Service.png){ width="50%" } 63 | 64 | 1. Select the **Satchel** model file you downloaded from GitHub. 65 | 66 | [GitHub Releases]: https://github.com/ryanlua/satchel/releases 67 | 68 | ## Wally 69 | 70 | You are expected to already have Wally setup in your Rojo project and basic knowledge on how to use Wally packages. 71 | 72 | !!! warning 73 | 74 | Wally does not include the loader script so you need to [`#!lua require()`][require] Satchel to run: 75 | ``` lua title="Satchel Loader" 76 | require(script.Satchel) 77 | ``` 78 | 79 | 1. Open your Rojo project in the code editor of your choice. 80 | 81 | 1. In the `wally.toml` file, add the [latest Wally version for Satchel][Wally]. Your dependencies should look similar to this: 82 | 83 | ``` toml title="wally.toml" 84 | [dependencies] 85 | satchel = "ryanlua/satchel@1.0.0" 86 | ``` 87 | 88 | 1. Install Satchel from Wally by running `wally install`. 89 | 90 | [Wally]: https://wally.run/package/ryanlua/satchel 91 | [require]: https://create.roblox.com/docs/reference/engine/globals/LuaGlobals#require 92 | -------------------------------------------------------------------------------- /docs/api-reference.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | Satchel is a reskin of the default BackpackGui located in [CoreGui]. Satchel acts very similar to the default backpack and is based on a fork on the default backpack. Behaviors between the two should remain the same with both of them managing the [Backpack]. 12 | 13 | [CoreGui]: https://create.roblox.com/docs/reference/engine/classes/CoreGui 14 | [Backpack]: https://create.roblox.com/docs/reference/engine/classes/Backpack 15 | 16 | ## Summary 17 | 18 | ### Attributes 19 | 20 | | Attribute | Description | Default | 21 | | :--- | :--- | :--- | 22 | | BackgroundColor3: [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) | Determines the background color of the default inventory window and slots. | `[25, 27, 29]` | 23 | | BackgroundTransparency: [`number`](https://create.roblox.com/docs/scripting/luau/numbers) | Determines the background transparency of the default inventory window and slots. | 0.3 | 24 | | CornerRadius: [`UDim`](https://create.roblox.com/docs/reference/engine/datatypes/UDim) | Determines the radius, in pixels, of the default inventory window and slots. | `0, 8` | 25 | | EquipBorderColor3: [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) | Determines the color of the equip border when a slot is equipped. | `[255, 255, 255]` | 26 | | EquipBorderSizePixel: [`number`](https://create.roblox.com/docs/scripting/luau/numbers) | Determines the pixel width of the equip border when a slot is equipped. | `5` | 27 | | FontFace: [`Font`](https://create.roblox.com/docs/reference/engine/enums/Font) | Determines the font of the default inventory window and slots. | `Builder Sans` | 28 | | InsetIconPadding: [`boolean`](https://create.roblox.com/docs/scripting/luau/booleans) | Determines whether or not the tool icon is padded in the default inventory window and slots. | True | 29 | | OutlineEquipBorder: [`boolean`](https://create.roblox.com/docs/scripting/luau/booleans) | Determines whether or not the equip border is outline or inset when a slot is equipped. | True | 30 | | TextColor3: [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) | Determines the color of the text in default inventory window and slots. | `[255, 255, 255]` | 31 | | TextSize: [`number`](https://create.roblox.com/docs/scripting/luau/numbers) | Determines the size of the text in the default inventory window and slots. | `14` | 32 | | TextStrokeColor3: [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) | Determines the color of the text stroke of text in default inventory window and slots. | `[0, 0, 0]` | 33 | | TextStrokeTransparency: [`number`](https://create.roblox.com/docs/scripting/luau/numbers) | Determines the transparency of the text stroke of text in default chat window and slots. | 0.5 | 34 | 35 | ### Methods 36 | 37 | | IsOpened(): [`boolean`](https://create.roblox.com/docs/scripting/luau/booleans) | 38 | | :--- | 39 | | Returns whether the inventory is opened or not. | 40 | 41 | | SetBackpackEnabled(enabled: boolean): `void` | 42 | | :--- | 43 | | Sets whether the backpack gui is enabled or disabled. | 44 | 45 | | GetBackpackEnabled(): [`boolean`](https://create.roblox.com/docs/scripting/luau/booleans) | 46 | | :--- | 47 | | Returns whether the backpack gui is enabled or disabled. | 48 | 49 | | GetStateChangedEvent(): [`RBXScriptSignal`](https://create.roblox.com/docs/reference/engine/datatypes/RBXScriptSignal) | 50 | | :--- | 51 | | Returns a signal that fires when the inventory is opened or closed. | 52 | 53 | ## Attributes 54 | 55 | ### BackgroundColor3 56 | 57 | [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) 58 | 59 | Determines the background color of the default inventory window and slots. Changing this will update the background color for all elements excluding the search box background for visibility purposes. 60 | 61 | ### BackgroundTransparency 62 | 63 | [`number`](https://create.roblox.com/docs/luau/numbers) 64 | 65 | Determines the background transparency of the default inventory window and slots. This will change how the hot bar looks in its locked state and the inventory background. 66 | 67 | ### CornerRadius 68 | 69 | [`UDim`](https://create.roblox.com/docs/reference/engine/datatypes/UDim) 70 | 71 | Determines the radius, in pixels, of the default inventory window and slots. This will affect all elements with a visible rounded corner. The corner radius for the search bar is calculated automatically based on this value. 72 | 73 | ### EquipBorderColor3 74 | 75 | [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) 76 | 77 | Determines the color of the equip border when a slot is equipped. The drag outline color of the slot will not changed by this. 78 | 79 | ### EquipBorderSizePixel 80 | 81 | [`number`](https://create.roblox.com/docs/luau/numbers) 82 | 83 | Determines the pixel width of the equip border when a slot is equipped. This additionally controls the padding of tool icons. 84 | 85 | ### FontFace 86 | 87 | [`Enum.Font`](https://create.roblox.com/docs/reference/engine/enums/Font) 88 | 89 | Determines the font of the default inventory window and slots. This includes all text in the Satchel UI. 90 | 91 | !!! bug 92 | 93 | Rojo does not support the [Font](https://create.roblox.com/docs/reference/engine/datatypes/Font) instance attribute so the it will not be synced. You may add the attribute manually if you wish to adjust the font. 94 | 95 | ### InsetIconPadding 96 | 97 | [`bool`](https://create.roblox.com/docs/luau/booleans) 98 | 99 | Determines whether or not the tool icon is padded in the default inventory window and slots. Changing this will change how the tool icon is padded in the slot or not. 100 | 101 | ### OutlineEquipBorder 102 | 103 | [`bool`](https://create.roblox.com/docs/luau/booleans) 104 | 105 | Determines whether or not the equip border is outline or inset when a slot is equipped. Changing this will make the equip border either border will outline or inset the slot. 106 | 107 | ### TextColor3 108 | 109 | [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) 110 | 111 | Determines the color of the text in default inventory window and slots. This will change the color of all text. 112 | 113 | ### TextSize 114 | 115 | [`number`](https://create.roblox.com/docs/luau/numbers) 116 | 117 | Determines the size of the text in the default inventory window and slots. This will change the text size of the tool names and will not change other text like search text, hotkey number, and gamepad hints. 118 | 119 | ### TextStrokeColor3 120 | 121 | [`Color3`](https://create.roblox.com/docs/reference/engine/datatypes/Color3) 122 | 123 | Determines the color of the text stroke of text in default inventory window and slots. This will change the color of all text strokes which are visible. 124 | 125 | ### TextStrokeTransparency 126 | 127 | [`number`](https://create.roblox.com/docs/luau/numbers) 128 | 129 | Determines the transparency of the text stroke of text in default chat window and slots. This will change all text strokes in which text strokes are visible. 130 | 131 | ## Methods 132 | 133 | ### IsOpened 134 | 135 | Returns whether the inventory is opened or not. 136 | 137 | #### Returns 138 | 139 | 140 | 141 | 142 | 143 |
bool
144 | 145 | ### SetBackpackEnabled 146 | 147 | Sets whether the backpack gui is enabled or disabled. 148 | 149 | #### Code Samples 150 | 151 | This code sample will disable the backpack gui. 152 | 153 | ``` lua title="Disable Backpack" 154 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 155 | 156 | local Satchel = require(ReplicatedStorage.Satchel) 157 | 158 | Satchel.SetBackpackEnabled(false) 159 | ``` 160 | 161 | #### Parameters 162 | 163 | 164 | 165 | 166 | 167 | 168 |
enabled: boolWhether to enable or disable the Backpack
169 | 170 | #### Returns 171 | 172 | 173 | 174 | 175 | 176 |
void
177 | 178 | ### GetBackpackEnabled 179 | 180 | Returns whether the backpack gui is enabled or disabled. 181 | 182 | #### Code Samples 183 | 184 | This code sample makes a TextButton that toggles the inventory when clicked. 185 | 186 | ``` lua title="Toggle Satchel" 187 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 188 | 189 | local Satchel = require(ReplicatedStorage.Satchel) 190 | 191 | local button = Instance.new("TextButton") 192 | button.AnchorPoint = Vector2.new(0.5, 0.5) 193 | button.Position = UDim2.new(0.5, 0, 0.5, 0) 194 | button.Text = "Toggle Inventory" 195 | button.MouseButton1Click:Connect(function() 196 | if Satchel:GetBackpackEnabled() then 197 | Satchel.SetBackpackEnabled(false) 198 | else 199 | Satchel.SetBackpackEnabled(true) 200 | end 201 | end) 202 | ``` 203 | 204 | #### Returns 205 | 206 | 207 | 208 | 209 | 210 |
bool
211 | 212 | ### GetStateChangedEvent 213 | 214 | Returns a signal that fires when the inventory is opened or closed. 215 | 216 | #### Code Samples 217 | 218 | This code sample detects when the inventory is opened or closed. 219 | 220 | ``` lua title="Detect Inventory State" 221 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 222 | 223 | local Satchel = require(ReplicatedStorage.Satchel) 224 | 225 | Satchel.GetStateChangedEvent():Connect(function(isOpened: boolean) 226 | if isOpened then 227 | print("Inventory opened") 228 | else 229 | print("Inventory closed") 230 | end 231 | end) 232 | ``` 233 | 234 | #### Returns 235 | 236 | 237 | 238 | 239 | 240 |
RBXScriptSignal
241 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | ### 1. Definitions 5 | 6 | **1.1. “Contributor”** 7 | means each individual or legal entity that creates, contributes to 8 | the creation of, or owns Covered Software. 9 | 10 | **1.2. “Contributor Version”** 11 | means the combination of the Contributions of others (if any) used 12 | by a Contributor and that particular Contributor's Contribution. 13 | 14 | **1.3. “Contribution”** 15 | means Covered Software of a particular Contributor. 16 | 17 | **1.4. “Covered Software”** 18 | means Source Code Form to which the initial Contributor has attached 19 | the notice in Exhibit A, the Executable Form of such Source Code 20 | Form, and Modifications of such Source Code Form, in each case 21 | including portions thereof. 22 | 23 | **1.5. “Incompatible With Secondary Licenses”** 24 | means 25 | 26 | * **(a)** that the initial Contributor has attached the notice described 27 | in Exhibit B to the Covered Software; or 28 | * **(b)** that the Covered Software was made available under the terms of 29 | version 1.1 or earlier of the License, but not also under the 30 | terms of a Secondary License. 31 | 32 | **1.6. “Executable Form”** 33 | means any form of the work other than Source Code Form. 34 | 35 | **1.7. “Larger Work”** 36 | means a work that combines Covered Software with other material, in 37 | a separate file or files, that is not Covered Software. 38 | 39 | **1.8. “License”** 40 | means this document. 41 | 42 | **1.9. “Licensable”** 43 | means having the right to grant, to the maximum extent possible, 44 | whether at the time of the initial grant or subsequently, any and 45 | all of the rights conveyed by this License. 46 | 47 | **1.10. “Modifications”** 48 | means any of the following: 49 | 50 | * **(a)** any file in Source Code Form that results from an addition to, 51 | deletion from, or modification of the contents of Covered 52 | Software; or 53 | * **(b)** any new file in Source Code Form that contains any Covered 54 | Software. 55 | 56 | **1.11. “Patent Claims” of a Contributor** 57 | means any patent claim(s), including without limitation, method, 58 | process, and apparatus claims, in any patent Licensable by such 59 | Contributor that would be infringed, but for the grant of the 60 | License, by the making, using, selling, offering for sale, having 61 | made, import, or transfer of either its Contributions or its 62 | Contributor Version. 63 | 64 | **1.12. “Secondary License”** 65 | means either the GNU General Public License, Version 2.0, the GNU 66 | Lesser General Public License, Version 2.1, the GNU Affero General 67 | Public License, Version 3.0, or any later versions of those 68 | licenses. 69 | 70 | **1.13. “Source Code Form”** 71 | means the form of the work preferred for making modifications. 72 | 73 | **1.14. “You” (or “Your”)** 74 | means an individual or a legal entity exercising rights under this 75 | License. For legal entities, “You” includes any entity that 76 | controls, is controlled by, or is under common control with You. For 77 | purposes of this definition, “control” means **(a)** the power, direct 78 | or indirect, to cause the direction or management of such entity, 79 | whether by contract or otherwise, or **(b)** ownership of more than 80 | fifty percent (50%) of the outstanding shares or beneficial 81 | ownership of such entity. 82 | 83 | 84 | ### 2. License Grants and Conditions 85 | 86 | #### 2.1. Grants 87 | 88 | Each Contributor hereby grants You a world-wide, royalty-free, 89 | non-exclusive license: 90 | 91 | * **(a)** under intellectual property rights (other than patent or trademark) 92 | Licensable by such Contributor to use, reproduce, make available, 93 | modify, display, perform, distribute, and otherwise exploit its 94 | Contributions, either on an unmodified basis, with Modifications, or 95 | as part of a Larger Work; and 96 | * **(b)** under Patent Claims of such Contributor to make, use, sell, offer 97 | for sale, have made, import, and otherwise transfer either its 98 | Contributions or its Contributor Version. 99 | 100 | #### 2.2. Effective Date 101 | 102 | The licenses granted in Section 2.1 with respect to any Contribution 103 | become effective for each Contribution on the date the Contributor first 104 | distributes such Contribution. 105 | 106 | #### 2.3. Limitations on Grant Scope 107 | 108 | The licenses granted in this Section 2 are the only rights granted under 109 | this License. No additional rights or licenses will be implied from the 110 | distribution or licensing of Covered Software under this License. 111 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 112 | Contributor: 113 | 114 | * **(a)** for any code that a Contributor has removed from Covered Software; 115 | or 116 | * **(b)** for infringements caused by: **(i)** Your and any other third party's 117 | modifications of Covered Software, or **(ii)** the combination of its 118 | Contributions with other software (except as part of its Contributor 119 | Version); or 120 | * **(c)** under Patent Claims infringed by Covered Software in the absence of 121 | its Contributions. 122 | 123 | This License does not grant any rights in the trademarks, service marks, 124 | or logos of any Contributor (except as may be necessary to comply with 125 | the notice requirements in Section 3.4). 126 | 127 | #### 2.4. Subsequent Licenses 128 | 129 | No Contributor makes additional grants as a result of Your choice to 130 | distribute the Covered Software under a subsequent version of this 131 | License (see Section 10.2) or under the terms of a Secondary License (if 132 | permitted under the terms of Section 3.3). 133 | 134 | #### 2.5. Representation 135 | 136 | Each Contributor represents that the Contributor believes its 137 | Contributions are its original creation(s) or it has sufficient rights 138 | to grant the rights to its Contributions conveyed by this License. 139 | 140 | #### 2.6. Fair Use 141 | 142 | This License is not intended to limit any rights You have under 143 | applicable copyright doctrines of fair use, fair dealing, or other 144 | equivalents. 145 | 146 | #### 2.7. Conditions 147 | 148 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 149 | in Section 2.1. 150 | 151 | 152 | ### 3. Responsibilities 153 | 154 | #### 3.1. Distribution of Source Form 155 | 156 | All distribution of Covered Software in Source Code Form, including any 157 | Modifications that You create or to which You contribute, must be under 158 | the terms of this License. You must inform recipients that the Source 159 | Code Form of the Covered Software is governed by the terms of this 160 | License, and how they can obtain a copy of this License. You may not 161 | attempt to alter or restrict the recipients' rights in the Source Code 162 | Form. 163 | 164 | #### 3.2. Distribution of Executable Form 165 | 166 | If You distribute Covered Software in Executable Form then: 167 | 168 | * **(a)** such Covered Software must also be made available in Source Code 169 | Form, as described in Section 3.1, and You must inform recipients of 170 | the Executable Form how they can obtain a copy of such Source Code 171 | Form by reasonable means in a timely manner, at a charge no more 172 | than the cost of distribution to the recipient; and 173 | 174 | * **(b)** You may distribute such Executable Form under the terms of this 175 | License, or sublicense it under different terms, provided that the 176 | license for the Executable Form does not attempt to limit or alter 177 | the recipients' rights in the Source Code Form under this License. 178 | 179 | #### 3.3. Distribution of a Larger Work 180 | 181 | You may create and distribute a Larger Work under terms of Your choice, 182 | provided that You also comply with the requirements of this License for 183 | the Covered Software. If the Larger Work is a combination of Covered 184 | Software with a work governed by one or more Secondary Licenses, and the 185 | Covered Software is not Incompatible With Secondary Licenses, this 186 | License permits You to additionally distribute such Covered Software 187 | under the terms of such Secondary License(s), so that the recipient of 188 | the Larger Work may, at their option, further distribute the Covered 189 | Software under the terms of either this License or such Secondary 190 | License(s). 191 | 192 | #### 3.4. Notices 193 | 194 | You may not remove or alter the substance of any license notices 195 | (including copyright notices, patent notices, disclaimers of warranty, 196 | or limitations of liability) contained within the Source Code Form of 197 | the Covered Software, except that You may alter any license notices to 198 | the extent required to remedy known factual inaccuracies. 199 | 200 | #### 3.5. Application of Additional Terms 201 | 202 | You may choose to offer, and to charge a fee for, warranty, support, 203 | indemnity or liability obligations to one or more recipients of Covered 204 | Software. However, You may do so only on Your own behalf, and not on 205 | behalf of any Contributor. You must make it absolutely clear that any 206 | such warranty, support, indemnity, or liability obligation is offered by 207 | You alone, and You hereby agree to indemnify every Contributor for any 208 | liability incurred by such Contributor as a result of warranty, support, 209 | indemnity or liability terms You offer. You may include additional 210 | disclaimers of warranty and limitations of liability specific to any 211 | jurisdiction. 212 | 213 | 214 | ### 4. Inability to Comply Due to Statute or Regulation 215 | 216 | If it is impossible for You to comply with any of the terms of this 217 | License with respect to some or all of the Covered Software due to 218 | statute, judicial order, or regulation then You must: **(a)** comply with 219 | the terms of this License to the maximum extent possible; and **(b)** 220 | describe the limitations and the code they affect. Such description must 221 | be placed in a text file included with all distributions of the Covered 222 | Software under this License. Except to the extent prohibited by statute 223 | or regulation, such description must be sufficiently detailed for a 224 | recipient of ordinary skill to be able to understand it. 225 | 226 | 227 | ### 5. Termination 228 | 229 | **5.1.** The rights granted under this License will terminate automatically 230 | if You fail to comply with any of its terms. However, if You become 231 | compliant, then the rights granted under this License from a particular 232 | Contributor are reinstated **(a)** provisionally, unless and until such 233 | Contributor explicitly and finally terminates Your grants, and **(b)** on an 234 | ongoing basis, if such Contributor fails to notify You of the 235 | non-compliance by some reasonable means prior to 60 days after You have 236 | come back into compliance. Moreover, Your grants from a particular 237 | Contributor are reinstated on an ongoing basis if such Contributor 238 | notifies You of the non-compliance by some reasonable means, this is the 239 | first time You have received notice of non-compliance with this License 240 | from such Contributor, and You become compliant prior to 30 days after 241 | Your receipt of the notice. 242 | 243 | **5.2.** If You initiate litigation against any entity by asserting a patent 244 | infringement claim (excluding declaratory judgment actions, 245 | counter-claims, and cross-claims) alleging that a Contributor Version 246 | directly or indirectly infringes any patent, then the rights granted to 247 | You by any and all Contributors for the Covered Software under Section 248 | 2.1 of this License shall terminate. 249 | 250 | **5.3.** In the event of termination under Sections 5.1 or 5.2 above, all 251 | end user license agreements (excluding distributors and resellers) which 252 | have been validly granted by You or Your distributors under this License 253 | prior to termination shall survive termination. 254 | 255 | 256 | ### 6. Disclaimer of Warranty 257 | 258 | > Covered Software is provided under this License on an “as is” 259 | > basis, without warranty of any kind, either expressed, implied, or 260 | > statutory, including, without limitation, warranties that the 261 | > Covered Software is free of defects, merchantable, fit for a 262 | > particular purpose or non-infringing. The entire risk as to the 263 | > quality and performance of the Covered Software is with You. 264 | > Should any Covered Software prove defective in any respect, You 265 | > (not any Contributor) assume the cost of any necessary servicing, 266 | > repair, or correction. This disclaimer of warranty constitutes an 267 | > essential part of this License. No use of any Covered Software is 268 | > authorized under this License except under this disclaimer. 269 | 270 | ### 7. Limitation of Liability 271 | 272 | > Under no circumstances and under no legal theory, whether tort 273 | > (including negligence), contract, or otherwise, shall any 274 | > Contributor, or anyone who distributes Covered Software as 275 | > permitted above, be liable to You for any direct, indirect, 276 | > special, incidental, or consequential damages of any character 277 | > including, without limitation, damages for lost profits, loss of 278 | > goodwill, work stoppage, computer failure or malfunction, or any 279 | > and all other commercial damages or losses, even if such party 280 | > shall have been informed of the possibility of such damages. This 281 | > limitation of liability shall not apply to liability for death or 282 | > personal injury resulting from such party's negligence to the 283 | > extent applicable law prohibits such limitation. Some 284 | > jurisdictions do not allow the exclusion or limitation of 285 | > incidental or consequential damages, so this exclusion and 286 | > limitation may not apply to You. 287 | 288 | 289 | ### 8. Litigation 290 | 291 | Any litigation relating to this License may be brought only in the 292 | courts of a jurisdiction where the defendant maintains its principal 293 | place of business and such litigation shall be governed by laws of that 294 | jurisdiction, without reference to its conflict-of-law provisions. 295 | Nothing in this Section shall prevent a party's ability to bring 296 | cross-claims or counter-claims. 297 | 298 | 299 | ### 9. Miscellaneous 300 | 301 | This License represents the complete agreement concerning the subject 302 | matter hereof. If any provision of this License is held to be 303 | unenforceable, such provision shall be reformed only to the extent 304 | necessary to make it enforceable. Any law or regulation which provides 305 | that the language of a contract shall be construed against the drafter 306 | shall not be used to construe this License against a Contributor. 307 | 308 | 309 | ### 10. Versions of the License 310 | 311 | #### 10.1. New Versions 312 | 313 | Mozilla Foundation is the license steward. Except as provided in Section 314 | 10.3, no one other than the license steward has the right to modify or 315 | publish new versions of this License. Each version will be given a 316 | distinguishing version number. 317 | 318 | #### 10.2. Effect of New Versions 319 | 320 | You may distribute the Covered Software under the terms of the version 321 | of the License under which You originally received the Covered Software, 322 | or under the terms of any subsequent version published by the license 323 | steward. 324 | 325 | #### 10.3. Modified Versions 326 | 327 | If you create software not governed by this License, and you want to 328 | create a new license for such software, you may create and use a 329 | modified version of this License if you rename the license and remove 330 | any references to the name of the license steward (except to note that 331 | such modified license differs from this License). 332 | 333 | #### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 334 | 335 | If You choose to distribute Source Code Form that is Incompatible With 336 | Secondary Licenses under the terms of this version of the License, the 337 | notice described in Exhibit B of this License must be attached. 338 | 339 | ## Exhibit A - Source Code Form License Notice 340 | 341 | This Source Code Form is subject to the terms of the Mozilla Public 342 | License, v. 2.0. If a copy of the MPL was not distributed with this 343 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 344 | 345 | If it is not possible or desirable to put the notice in a particular 346 | file, then You may include the notice in a location (such as a LICENSE 347 | file in a relevant directory) where a recipient would be likely to look 348 | for such a notice. 349 | 350 | You may add additional accurate notices of copyright ownership. 351 | 352 | ## Exhibit B - “Incompatible With Secondary Licenses” Notice 353 | 354 | This Source Code Form is "Incompatible With Secondary Licenses", as 355 | defined by the Mozilla Public License, v. 2.0. 356 | 357 | -------------------------------------------------------------------------------- /src/init.luau: -------------------------------------------------------------------------------- 1 | --!nolint DeprecatedApi 2 | 3 | --[[ 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | ]] 8 | 9 | local ContextActionService = game:GetService("ContextActionService") 10 | local TextChatService = game:GetService("TextChatService") 11 | local UserInputService = game:GetService("UserInputService") 12 | local StarterGui = game:GetService("StarterGui") 13 | local GuiService = game:GetService("GuiService") 14 | local RunService = game:GetService("RunService") 15 | local VRService = game:GetService("VRService") 16 | local Players = game:GetService("Players") 17 | local PlayerGui: Instance = Players.LocalPlayer:WaitForChild("PlayerGui") 18 | 19 | local BackpackScript = {} 20 | 21 | BackpackScript.OpenClose = nil :: any -- Function to toggle open/close 22 | BackpackScript.IsOpen = false :: boolean 23 | BackpackScript.StateChanged = Instance.new("BindableEvent") :: BindableEvent -- Fires after any open/close, passes IsNowOpen 24 | 25 | BackpackScript.ModuleName = "Backpack" :: string 26 | BackpackScript.KeepVRTopbarOpen = true :: boolean 27 | BackpackScript.VRIsExclusive = true :: boolean 28 | BackpackScript.VRClosesNonExclusive = true :: boolean 29 | 30 | BackpackScript.BackpackEmpty = Instance.new("BindableEvent") :: BindableEvent -- Fires when the backpack is empty (no tools 31 | BackpackScript.BackpackEmpty.Name = "BackpackEmpty" 32 | 33 | BackpackScript.BackpackItemAdded = Instance.new("BindableEvent") :: BindableEvent -- Fires when an item is added to the backpack 34 | BackpackScript.BackpackItemAdded.Name = "BackpackAdded" 35 | 36 | BackpackScript.BackpackItemRemoved = Instance.new("BindableEvent") :: BindableEvent -- Fires when an item is removed from the backpack 37 | BackpackScript.BackpackItemRemoved.Name = "BackpackRemoved" 38 | 39 | local targetScript: ModuleScript = script 40 | 41 | require(script.Attribution) 42 | 43 | -- Constants -- 44 | local PREFERRED_TRANSPARENCY: number = GuiService.PreferredTransparency or 1 45 | 46 | -- Legacy behavior for backpack 47 | local LEGACY_EDGE_ENABLED: boolean = not targetScript:GetAttribute("OutlineEquipBorder") or false -- Instead of the edge selection being inset, it will be on the outlined. LEGACY_PADDING must be enabled for this to work or this will do nothing 48 | local LEGACY_PADDING_ENABLED: boolean = targetScript:GetAttribute("InsetIconPadding") -- Instead of the icon taking up the full slot, it will be padded on each side. 49 | 50 | -- Background 51 | local BACKGROUND_TRANSPARENCY_DEFAULT: number = targetScript:GetAttribute("BackgroundTransparency") or 0.3 52 | local BACKGROUND_TRANSPARENCY: number = BACKGROUND_TRANSPARENCY_DEFAULT * PREFERRED_TRANSPARENCY 53 | local BACKGROUND_CORNER_RADIUS: UDim = UDim.new(0, 8) 54 | local BACKGROUND_COLOR: Color3 = targetScript:GetAttribute("BackgroundColor3") 55 | or Color3.new(25 / 255, 27 / 255, 29 / 255) 56 | 57 | -- Slots 58 | local SLOT_EQUIP_COLOR: Color3 = targetScript:GetAttribute("EquipBorderColor3") or Color3.new(0 / 255, 162 / 255, 1) 59 | local SLOT_LOCKED_TRANSPARENCY_DEFAULT: number = targetScript:GetAttribute("BackgroundTransparency") or 0.3 -- Locked means undraggable 60 | local SLOT_LOCKED_TRANSPARENCY: number = SLOT_LOCKED_TRANSPARENCY_DEFAULT * PREFERRED_TRANSPARENCY 61 | local SLOT_EQUIP_THICKNESS: number = targetScript:GetAttribute("EquipBorderSizePixel") or 5 -- Relative 62 | local SLOT_CORNER_RADIUS: UDim = targetScript:GetAttribute("CornerRadius") or UDim.new(0, 8) 63 | local SLOT_BORDER_COLOR: Color3 = Color3.new(1, 1, 1) -- Appears when dragging 64 | 65 | -- Tooltips 66 | local TOOLTIP_CORNER_RADIUS: UDim = SLOT_CORNER_RADIUS - UDim.new(0, 5) or UDim.new(0, 3) 67 | local TOOLTIP_BACKGROUND_COLOR: Color3 = targetScript:GetAttribute("BackgroundColor3") 68 | or Color3.new(25 / 255, 27 / 255, 29 / 255) 69 | local TOOLTIP_PADDING: number = 4 70 | local TOOLTIP_HEIGHT: number = 16 71 | local TOOLTIP_OFFSET: number = -5 -- From to 72 | 73 | -- Topbar icons 74 | local ARROW_IMAGE_OPEN: string = "rbxasset://textures/ui/TopBar/inventoryOn.png" 75 | local ARROW_IMAGE_CLOSE: string = "rbxasset://textures/ui/TopBar/inventoryOff.png" 76 | -- local ARROW_HOTKEY: { Enum.KeyCode } = { Enum.KeyCode.Backquote, Enum.KeyCode.DPadUp } --TODO: Hookup '~' too? 77 | 78 | -- Hotbar slots 79 | local HOTBAR_SLOTS_FULL: number = 10 -- 10 is the max 80 | local HOTBAR_SLOTS_VR: number = 6 81 | local HOTBAR_SLOTS_MINI: number = 6 -- Mobile gets 6 slots instead of default 3 it had before 82 | local HOTBAR_SLOTS_WIDTH_CUTOFF: number = 1024 -- Anything smaller is MINI 83 | 84 | local INVENTORY_ROWS_FULL: number = 4 85 | local INVENTORY_ROWS_VR: number = 3 86 | local INVENTORY_ROWS_MINI: number = 2 87 | local INVENTORY_HEADER_SIZE: number = 40 88 | local INVENTORY_ARROWS_BUFFER_VR: number = 40 89 | 90 | -- Text 91 | local TEXT_COLOR: Color3 = targetScript:GetAttribute("TextColor3") or Color3.new(1, 1, 1) 92 | local TEXT_STROKE_TRANSPARENCY: number = targetScript:GetAttribute("TextStrokeTransparency") or 0.5 93 | local TEXT_STROKE_COLOR: Color3 = targetScript:GetAttribute("TextStrokeColor3") or Color3.new(0, 0, 0) 94 | 95 | -- Search 96 | local SEARCH_BACKGROUND_COLOR: Color3 = Color3.new(25 / 255, 27 / 255, 29 / 255) 97 | local SEARCH_BACKGROUND_TRANSPARENCY_DEFAULT: number = 0.2 98 | local SEARCH_BACKGROUND_TRANSPARENCY: number = SEARCH_BACKGROUND_TRANSPARENCY_DEFAULT * PREFERRED_TRANSPARENCY 99 | local SEARCH_BORDER_COLOR: Color3 = Color3.new(1, 1, 1) 100 | local SEARCH_BORDER_TRANSPARENCY: number = 0.8 101 | local SEARCH_BORDER_THICKNESS: number = 1 102 | local SEARCH_TEXT_PLACEHOLDER: string = "Search" 103 | local SEARCH_TEXT_OFFSET: number = 8 104 | local SEARCH_TEXT: string = "" 105 | local SEARCH_CORNER_RADIUS: UDim = UDim.new(0, 3) 106 | local SEARCH_IMAGE_X: string = "rbxasset://textures/ui/InspectMenu/x.png" 107 | local SEARCH_BUFFER_PIXELS: number = 5 108 | local SEARCH_WIDTH_PIXELS: number = 200 109 | 110 | -- Misc 111 | local FONT_FAMILY: Font = targetScript:GetAttribute("FontFace") 112 | or Font.new("rbxasset://fonts/families/BuilderSans.json") 113 | local FONT_SIZE: number = targetScript:GetAttribute("TextSize") or 16 114 | local DROP_HOTKEY_VALUE: number = Enum.KeyCode.Backspace.Value 115 | local ZERO_KEY_VALUE: number = Enum.KeyCode.Zero.Value 116 | local DOUBLE_CLICK_TIME: number = 0.5 117 | local ICON_BUFFER_PIXELS: number = 5 118 | local ICON_SIZE_PIXELS: number = 60 119 | 120 | local MOUSE_INPUT_TYPES: { [Enum.UserInputType]: boolean } = 121 | { -- These are the input types that will be used for mouse -- [[ADDED]], Optional 122 | [Enum.UserInputType.MouseButton1] = true, 123 | [Enum.UserInputType.MouseButton2] = true, 124 | [Enum.UserInputType.MouseButton3] = true, 125 | [Enum.UserInputType.MouseMovement] = true, 126 | [Enum.UserInputType.MouseWheel] = true, 127 | } 128 | 129 | local GAMEPAD_INPUT_TYPES: { [Enum.UserInputType]: boolean } = 130 | { -- These are the input types that will be used for gamepad 131 | [Enum.UserInputType.Gamepad1] = true, 132 | [Enum.UserInputType.Gamepad2] = true, 133 | [Enum.UserInputType.Gamepad3] = true, 134 | [Enum.UserInputType.Gamepad4] = true, 135 | [Enum.UserInputType.Gamepad5] = true, 136 | [Enum.UserInputType.Gamepad6] = true, 137 | [Enum.UserInputType.Gamepad7] = true, 138 | [Enum.UserInputType.Gamepad8] = true, 139 | } 140 | 141 | -- Topbar logic 142 | local BackpackEnabled: boolean = true 143 | 144 | local Icon: any = require(script.Parent.topbarplus) 145 | 146 | local inventoryIcon: any = Icon.new() 147 | :setName("Inventory") 148 | :setImage(ARROW_IMAGE_OPEN, "Selected") 149 | :setImage(ARROW_IMAGE_CLOSE, "Deselected") 150 | :setImageScale(1) 151 | :setCaption("Inventory") 152 | :bindToggleKey(Enum.KeyCode.Backquote) 153 | :autoDeselect(false) 154 | :setOrder(-1) 155 | 156 | inventoryIcon.toggled:Connect(function(): () 157 | if not GuiService.MenuIsOpen then 158 | BackpackScript.OpenClose() 159 | end 160 | end) 161 | 162 | local BackpackGui: ScreenGui = Instance.new("ScreenGui") 163 | BackpackGui.DisplayOrder = 120 164 | BackpackGui.IgnoreGuiInset = true 165 | BackpackGui.ResetOnSpawn = false 166 | BackpackGui.Name = "BackpackGui" 167 | BackpackGui.Parent = PlayerGui 168 | 169 | local IsTenFootInterface: boolean = GuiService:IsTenFootInterface() 170 | if IsTenFootInterface then 171 | ICON_SIZE_PIXELS = 100 172 | FONT_SIZE = 24 173 | end 174 | 175 | local GamepadActionsBound: boolean = false 176 | 177 | local IS_PHONE: boolean = UserInputService.TouchEnabled 178 | and workspace.CurrentCamera.ViewportSize.X < HOTBAR_SLOTS_WIDTH_CUTOFF 179 | 180 | local Player: Player = Players.LocalPlayer 181 | 182 | local MainFrame: Frame = nil 183 | local HotbarFrame: Frame = nil 184 | local InventoryFrame: Frame = nil 185 | local VRInventorySelector: any = nil 186 | local ScrollingFrame: ScrollingFrame = nil 187 | local UIGridFrame: Frame = nil 188 | local UIGridLayout: UIGridLayout = nil 189 | local ScrollUpInventoryButton: any = nil 190 | local ScrollDownInventoryButton: any = nil 191 | local changeToolFunc: any = nil 192 | 193 | local Character: Model = Player.Character or Player.CharacterAdded:Wait() 194 | local Humanoid: any = Character:WaitForChild("Humanoid") 195 | local Backpack: Instance = Player:WaitForChild("Backpack") 196 | 197 | local Slots = {} -- List of all Slots by index 198 | local LowestEmptySlot: any = nil 199 | local SlotsByTool = {} -- Map of Tools to their assigned Slots 200 | local HotkeyFns = {} -- Map of KeyCode values to their assigned behaviors 201 | local Dragging: { boolean } = {} -- Only used to check if anything is being dragged, to disable other input 202 | local FullHotbarSlots: number = 0 -- Now being used to also determine whether or not LB and RB on the gamepad are enabled. 203 | local ActiveHopper = nil -- NOTE: HopperBin 204 | local StarterToolFound: boolean = false -- Special handling is required for the gear currently equipped on the site 205 | local WholeThingEnabled: boolean = false 206 | local TextBoxFocused: boolean = false -- ANY TextBox, not just the search box 207 | local ViewingSearchResults: boolean = false -- If the results of a search are currently being viewed 208 | -- local HotkeyStrings = {} -- Used for eating/releasing hotkeys 209 | local CharConns: { RBXScriptConnection } = {} -- Holds character Connections to be cleared later 210 | local GamepadEnabled: boolean = false -- determines if our gui needs to be gamepad friendly 211 | 212 | local IsVR: boolean = VRService.VREnabled -- Are we currently using a VR device? 213 | local NumberOfHotbarSlots: number = IsVR and HOTBAR_SLOTS_VR or (IS_PHONE and HOTBAR_SLOTS_MINI or HOTBAR_SLOTS_FULL) -- Number of slots shown at the bottom 214 | local NumberOfInventoryRows: number = IsVR and INVENTORY_ROWS_VR 215 | or (IS_PHONE and INVENTORY_ROWS_MINI or INVENTORY_ROWS_FULL) -- How many rows in the popped-up inventory 216 | local BackpackPanel = nil 217 | local lastEquippedSlot: any = nil 218 | 219 | local function EvaluateBackpackPanelVisibility(enabled: boolean): boolean 220 | return enabled and inventoryIcon.enabled and BackpackEnabled and VRService.VREnabled 221 | end 222 | 223 | local function ShowVRBackpackPopup(): () 224 | if BackpackPanel and EvaluateBackpackPanelVisibility(true) then 225 | BackpackPanel:ForceShowForSeconds(2) 226 | end 227 | end 228 | 229 | local function FindLowestEmpty(): number? 230 | for i: number = 1, NumberOfHotbarSlots do 231 | local slot: any = Slots[i] 232 | if not slot.Tool then 233 | return slot 234 | end 235 | end 236 | return nil 237 | end 238 | 239 | local function isInventoryEmpty(): boolean 240 | for i: number = NumberOfHotbarSlots + 1, #Slots do 241 | local slot: any = Slots[i] 242 | if slot and slot.Tool then 243 | return false 244 | end 245 | end 246 | return true 247 | end 248 | 249 | BackpackScript.IsInventoryEmpty = isInventoryEmpty 250 | 251 | local function UseGazeSelection(): boolean 252 | return false -- disabled in new VR system 253 | end 254 | 255 | local function AdjustHotbarFrames(): () 256 | local inventoryOpen: boolean = InventoryFrame.Visible -- (Show all) 257 | local visualTotal: number = inventoryOpen and NumberOfHotbarSlots or FullHotbarSlots 258 | local visualIndex: number = 0 259 | 260 | for i: number = 1, NumberOfHotbarSlots do 261 | local slot: any = Slots[i] 262 | if slot.Tool or inventoryOpen then 263 | visualIndex = visualIndex + 1 264 | slot:Readjust(visualIndex, visualTotal) 265 | slot.Frame.Visible = true 266 | else 267 | slot.Frame.Visible = false 268 | end 269 | end 270 | end 271 | 272 | local function UpdateScrollingFrameCanvasSize(): () 273 | local countX: number = math.floor(ScrollingFrame.AbsoluteSize.X / (ICON_SIZE_PIXELS + ICON_BUFFER_PIXELS)) 274 | local maxRow: number = math.ceil((#UIGridFrame:GetChildren() - 1) / countX) 275 | local canvasSizeY: number = maxRow * (ICON_SIZE_PIXELS + ICON_BUFFER_PIXELS) + ICON_BUFFER_PIXELS 276 | ScrollingFrame.CanvasSize = UDim2.fromOffset(0, canvasSizeY) 277 | end 278 | 279 | local function AdjustInventoryFrames(): () 280 | for i: number = NumberOfHotbarSlots + 1, #Slots do 281 | local slot: any = Slots[i] 282 | slot.Frame.LayoutOrder = slot.Index 283 | slot.Frame.Visible = (slot.Tool ~= nil) 284 | end 285 | UpdateScrollingFrameCanvasSize() 286 | end 287 | 288 | local function UpdateBackpackLayout(): () 289 | HotbarFrame.Size = UDim2.new( 290 | 0, 291 | ICON_BUFFER_PIXELS + (NumberOfHotbarSlots * (ICON_SIZE_PIXELS + ICON_BUFFER_PIXELS)), 292 | 0, 293 | ICON_BUFFER_PIXELS + ICON_SIZE_PIXELS + ICON_BUFFER_PIXELS 294 | ) 295 | HotbarFrame.Position = UDim2.new(0.5, -HotbarFrame.Size.X.Offset / 2, 1, -HotbarFrame.Size.Y.Offset) 296 | InventoryFrame.Size = UDim2.new( 297 | 0, 298 | HotbarFrame.Size.X.Offset, 299 | 0, 300 | (HotbarFrame.Size.Y.Offset * NumberOfInventoryRows) 301 | + INVENTORY_HEADER_SIZE 302 | + (IsVR and 2 * INVENTORY_ARROWS_BUFFER_VR or 0) 303 | ) 304 | InventoryFrame.Position = UDim2.new( 305 | 0.5, 306 | -InventoryFrame.Size.X.Offset / 2, 307 | 1, 308 | HotbarFrame.Position.Y.Offset - InventoryFrame.Size.Y.Offset 309 | ) 310 | 311 | ScrollingFrame.Size = UDim2.new( 312 | 1, 313 | ScrollingFrame.ScrollBarThickness + 1, 314 | 1, 315 | -INVENTORY_HEADER_SIZE - (IsVR and 2 * INVENTORY_ARROWS_BUFFER_VR or 0) 316 | ) 317 | ScrollingFrame.Position = UDim2.fromOffset(0, INVENTORY_HEADER_SIZE + (IsVR and INVENTORY_ARROWS_BUFFER_VR or 0)) 318 | AdjustHotbarFrames() 319 | AdjustInventoryFrames() 320 | end 321 | 322 | local function Clamp(low: number, high: number, num: number): number 323 | return math.min(high, math.max(low, num)) 324 | end 325 | 326 | local function CheckBounds(guiObject: GuiObject, x: number, y: number): boolean 327 | local pos: Vector2 = guiObject.AbsolutePosition 328 | local size: Vector2 = guiObject.AbsoluteSize 329 | return (x > pos.X and x <= pos.X + size.X and y > pos.Y and y <= pos.Y + size.Y) 330 | end 331 | 332 | local function GetOffset(guiObject: GuiObject, point: Vector2): number 333 | local centerPoint: Vector2 = guiObject.AbsolutePosition + (guiObject.AbsoluteSize / 2) 334 | return (centerPoint - point).Magnitude 335 | end 336 | 337 | local function DisableActiveHopper(): () --NOTE: HopperBin 338 | ActiveHopper:ToggleSelect() 339 | SlotsByTool[ActiveHopper]:UpdateEquipView() 340 | ActiveHopper = nil :: any 341 | end 342 | 343 | local function UnequipAllTools(): () --NOTE: HopperBin 344 | if Humanoid then 345 | Humanoid:UnequipTools() 346 | if ActiveHopper then 347 | DisableActiveHopper() 348 | end 349 | end 350 | end 351 | 352 | local function EquipNewTool(tool: Tool): () --NOTE: HopperBin 353 | UnequipAllTools() 354 | Humanoid:EquipTool(tool) --NOTE: This would also unequip current Tool 355 | --tool.Parent = Character --TODO: Switch back to above line after EquipTool is fixed! 356 | end 357 | 358 | local function IsEquipped(tool: Tool): boolean 359 | return tool and tool.Parent == Character --NOTE: HopperBin 360 | end 361 | 362 | -- Create a slot 363 | local function MakeSlot(parent: Instance, initIndex: number?): GuiObject 364 | local index: number = initIndex or (#Slots + 1) 365 | 366 | -- Slot Definition -- 367 | 368 | local slot: any = {} 369 | slot.Tool = nil :: any 370 | slot.Index = index :: number 371 | slot.Frame = nil :: any 372 | 373 | local SlotFrame: any = nil 374 | local FakeSlotFrame: Frame = nil 375 | local ToolIcon: ImageLabel = nil 376 | local ToolName: TextLabel = nil 377 | local ToolChangeConn: any = nil 378 | local HighlightFrame: any = nil -- UIStroke 379 | local SelectionObj: ImageLabel = nil 380 | 381 | --NOTE: The following are only defined for Hotbar Slots 382 | local ToolTip: TextLabel = nil 383 | local SlotNumber: TextLabel = nil 384 | 385 | -- Slot Functions -- 386 | 387 | -- Update slot transparency 388 | local function UpdateSlotFading(): () 389 | SlotFrame.SelectionImageObject = nil 390 | SlotFrame.BackgroundTransparency = SlotFrame.Draggable and 0 or SLOT_LOCKED_TRANSPARENCY 391 | end 392 | 393 | -- Adjust the slots to the centered 394 | function slot:Readjust(visualIndex: number, visualTotal: number): ...any --NOTE: Only used for Hotbar slots 395 | local centered: number = HotbarFrame.Size.X.Offset / 2 396 | local sizePlus: number = ICON_BUFFER_PIXELS + ICON_SIZE_PIXELS 397 | local midpointish: number = (visualTotal / 2) + 0.5 398 | local factor: number = visualIndex - midpointish 399 | SlotFrame.Position = 400 | UDim2.fromOffset(centered - (ICON_SIZE_PIXELS / 2) + (sizePlus * factor), ICON_BUFFER_PIXELS) 401 | end 402 | 403 | -- Fill the slot with a tool 404 | function slot:Fill(tool: Tool): ...any 405 | -- Clear slot if it has no tool else assign the tool 406 | if not tool then 407 | return self:Clear() 408 | end 409 | 410 | self.Tool = tool :: Tool 411 | 412 | -- Update the slot with tool data 413 | local function assignToolData(): () 414 | local icon: string = tool.TextureId 415 | ToolIcon.Image = icon 416 | 417 | if icon ~= "" then 418 | -- Enable the tool name on the slot if there is no icon 419 | ToolName.Visible = false 420 | else 421 | ToolName.Visible = true 422 | end 423 | 424 | ToolName.Text = tool.Name 425 | 426 | -- If there is a tooltip, then show it 427 | if ToolTip and tool:IsA("Tool") then --NOTE: HopperBin 428 | ToolTip.Text = tool.ToolTip 429 | ToolTip.Size = UDim2.fromOffset(0, TOOLTIP_HEIGHT) 430 | ToolTip.Position = UDim2.new(0.5, 0, 0, TOOLTIP_OFFSET) 431 | end 432 | end 433 | assignToolData() 434 | 435 | -- Disconnect tool event if it exists 436 | if ToolChangeConn then 437 | ToolChangeConn:Disconnect() 438 | ToolChangeConn = nil 439 | end 440 | 441 | -- Update the slot with new tool data if the tool's properties changes 442 | ToolChangeConn = tool.Changed:Connect(function(property: string): () 443 | if property == "TextureId" or property == "Name" or property == "ToolTip" then 444 | assignToolData() 445 | end 446 | end) 447 | 448 | local hotbarSlot: boolean = (self.Index <= NumberOfHotbarSlots) 449 | local inventoryOpen: boolean = InventoryFrame.Visible 450 | 451 | if (not hotbarSlot or inventoryOpen) and not UserInputService.VREnabled then 452 | SlotFrame.Draggable = true 453 | end 454 | 455 | self:UpdateEquipView() 456 | 457 | if hotbarSlot then 458 | FullHotbarSlots = FullHotbarSlots + 1 459 | -- If using a controller, determine whether or not we can enable BindCoreAction("BackpackHotbarEquip", etc) 460 | if WholeThingEnabled and FullHotbarSlots >= 1 and not GamepadActionsBound then 461 | -- Player added first item to a hotbar slot, enable BindCoreAction 462 | GamepadActionsBound = true 463 | ContextActionService:BindAction( 464 | "BackpackHotbarEquip", 465 | changeToolFunc, 466 | false, 467 | Enum.KeyCode.ButtonL1, 468 | Enum.KeyCode.ButtonR1 469 | ) 470 | end 471 | end 472 | 473 | SlotsByTool[tool] = self 474 | LowestEmptySlot = FindLowestEmpty() 475 | end 476 | 477 | -- Empty the slot of any tool data 478 | function slot:Clear(): ...any 479 | if not self.Tool then 480 | return 481 | end 482 | 483 | -- Disconnect tool event if it exists 484 | if ToolChangeConn then 485 | ToolChangeConn:Disconnect() 486 | ToolChangeConn = nil 487 | end 488 | 489 | ToolIcon.Image = "" 490 | ToolName.Text = "" 491 | if ToolTip then 492 | ToolTip.Text = "" 493 | ToolTip.Visible = false 494 | end 495 | SlotFrame.Draggable = false 496 | 497 | self:UpdateEquipView(true) -- Show as unequipped 498 | 499 | if self.Index <= NumberOfHotbarSlots then 500 | FullHotbarSlots = FullHotbarSlots - 1 501 | if FullHotbarSlots < 1 then 502 | -- Player removed last item from hotbar; UnbindCoreAction("BackpackHotbarEquip"), allowing the developer to use LB and RB. 503 | GamepadActionsBound = false 504 | ContextActionService:UnbindAction("BackpackHotbarEquip") 505 | end 506 | end 507 | 508 | SlotsByTool[self.Tool] = nil 509 | self.Tool = nil 510 | LowestEmptySlot = FindLowestEmpty() 511 | end 512 | 513 | function slot:UpdateEquipView(unequippedOverride: boolean?): ...any 514 | local override = unequippedOverride or false 515 | if not override and IsEquipped(self.Tool) then -- Equipped 516 | lastEquippedSlot = slot 517 | if not HighlightFrame then 518 | HighlightFrame = Instance.new("UIStroke") 519 | HighlightFrame.Name = "Border" 520 | HighlightFrame.Thickness = SLOT_EQUIP_THICKNESS 521 | HighlightFrame.Color = SLOT_EQUIP_COLOR 522 | HighlightFrame.ApplyStrokeMode = Enum.ApplyStrokeMode.Border 523 | end 524 | if LEGACY_EDGE_ENABLED == true then 525 | HighlightFrame.Parent = ToolIcon 526 | else 527 | HighlightFrame.Parent = SlotFrame 528 | end 529 | else -- In the Backpack 530 | if HighlightFrame then 531 | HighlightFrame.Parent = nil 532 | end 533 | end 534 | UpdateSlotFading() 535 | end 536 | 537 | function slot:IsEquipped(): boolean 538 | return IsEquipped(self.Tool) 539 | end 540 | 541 | function slot:Delete(): ...any 542 | SlotFrame:Destroy() --NOTE: Also clears connections 543 | table.remove(Slots, self.Index) 544 | local newSize: number = #Slots 545 | 546 | -- Now adjust the rest (both visually and representationally) 547 | for slotIndex: number = self.Index :: number, newSize :: number do 548 | Slots[slotIndex]:SlideBack() 549 | end 550 | 551 | UpdateScrollingFrameCanvasSize() 552 | end 553 | 554 | function slot:Swap(targetSlot: any): ...any --NOTE: This slot (self) must not be empty! 555 | local myTool: any, otherTool: any = self.Tool, targetSlot.Tool 556 | self:Clear() 557 | if otherTool then -- (Target slot might be empty) 558 | targetSlot:Clear() 559 | self:Fill(otherTool) 560 | end 561 | if myTool then 562 | targetSlot:Fill(myTool) 563 | else 564 | targetSlot:Clear() 565 | end 566 | end 567 | 568 | function slot:SlideBack(): ...any -- For inventory slot shifting 569 | self.Index = self.Index - 1 570 | SlotFrame.Name = self.Index 571 | SlotFrame.LayoutOrder = self.Index 572 | end 573 | 574 | function slot:TurnNumber(on: boolean): ...any 575 | if SlotNumber then 576 | SlotNumber.Visible = on 577 | end 578 | end 579 | 580 | function slot:SetClickability(on: boolean): ...any -- (Happens on open/close arrow) 581 | if self.Tool then 582 | if UserInputService.VREnabled then 583 | SlotFrame.Draggable = false 584 | else 585 | SlotFrame.Draggable = not on 586 | end 587 | UpdateSlotFading() 588 | end 589 | end 590 | 591 | function slot:CheckTerms(terms: any): number 592 | local hits: number = 0 593 | local function checkEm(str: string, term: any): () 594 | local _, n: number = str:lower():gsub(term, "") 595 | hits = hits + n 596 | end 597 | local tool: Tool = self.Tool 598 | if tool then 599 | for term: any in pairs(terms) do 600 | checkEm(ToolName.Text, term) 601 | if tool:IsA("Tool") then --NOTE: HopperBin 602 | local toolTipText: string = ToolTip and ToolTip.Text or "" 603 | checkEm(toolTipText, term) 604 | end 605 | end 606 | end 607 | return hits 608 | end 609 | 610 | -- Slot select logic, activated by clicking or pressing hotkey 611 | function slot:Select(): ...any 612 | local tool: Tool = slot.Tool 613 | if tool then 614 | if IsEquipped(tool) then --NOTE: HopperBin 615 | UnequipAllTools() 616 | elseif tool.Parent == Backpack then 617 | EquipNewTool(tool) 618 | end 619 | end 620 | end 621 | 622 | -- Slot Init Logic -- 623 | 624 | SlotFrame = Instance.new("TextButton") 625 | SlotFrame.Name = tostring(index) 626 | SlotFrame.BackgroundColor3 = BACKGROUND_COLOR 627 | SlotFrame.BorderColor3 = SLOT_BORDER_COLOR 628 | SlotFrame.Text = "" 629 | SlotFrame.BorderSizePixel = 0 630 | SlotFrame.Size = UDim2.fromOffset(ICON_SIZE_PIXELS, ICON_SIZE_PIXELS) 631 | SlotFrame.Active = true 632 | SlotFrame.Draggable = false 633 | SlotFrame.BackgroundTransparency = SLOT_LOCKED_TRANSPARENCY 634 | SlotFrame.MouseButton1Click:Connect(function(): () 635 | changeSlot(slot) 636 | end) 637 | local searchFrameCorner: UICorner = Instance.new("UICorner") 638 | searchFrameCorner.Name = "Corner" 639 | searchFrameCorner.CornerRadius = SLOT_CORNER_RADIUS 640 | searchFrameCorner.Parent = SlotFrame 641 | slot.Frame = SlotFrame 642 | 643 | do 644 | local selectionObjectClipper: Frame = Instance.new("Frame") 645 | selectionObjectClipper.Name = "SelectionObjectClipper" 646 | selectionObjectClipper.BackgroundTransparency = 1 647 | selectionObjectClipper.Visible = false 648 | selectionObjectClipper.Parent = SlotFrame 649 | 650 | SelectionObj = Instance.new("ImageLabel") 651 | SelectionObj.Name = "Selector" 652 | SelectionObj.BackgroundTransparency = 1 653 | SelectionObj.Size = UDim2.fromScale(1, 1) 654 | SelectionObj.Image = "rbxasset://textures/ui/Keyboard/key_selection_9slice.png" 655 | SelectionObj.ScaleType = Enum.ScaleType.Slice 656 | SelectionObj.SliceCenter = Rect.new(12, 12, 52, 52) 657 | SelectionObj.Parent = selectionObjectClipper 658 | end 659 | 660 | ToolIcon = Instance.new("ImageLabel") 661 | ToolIcon.BackgroundTransparency = 1 662 | ToolIcon.Name = "Icon" 663 | ToolIcon.Size = UDim2.fromScale(1, 1) 664 | ToolIcon.Position = UDim2.fromScale(0.5, 0.5) 665 | ToolIcon.AnchorPoint = Vector2.new(0.5, 0.5) 666 | if LEGACY_PADDING_ENABLED == true then 667 | ToolIcon.Size = UDim2.new(1, -SLOT_EQUIP_THICKNESS * 2, 1, -SLOT_EQUIP_THICKNESS * 2) 668 | else 669 | ToolIcon.Size = UDim2.fromScale(1, 1) 670 | end 671 | ToolIcon.Parent = SlotFrame 672 | 673 | local ToolIconCorner: UICorner = Instance.new("UICorner") 674 | ToolIconCorner.Name = "Corner" 675 | if LEGACY_PADDING_ENABLED == true then 676 | ToolIconCorner.CornerRadius = SLOT_CORNER_RADIUS - UDim.new(0, SLOT_EQUIP_THICKNESS) 677 | else 678 | ToolIconCorner.CornerRadius = SLOT_CORNER_RADIUS 679 | end 680 | ToolIconCorner.Parent = ToolIcon 681 | 682 | ToolName = Instance.new("TextLabel") 683 | ToolName.BackgroundTransparency = 1 684 | ToolName.Name = "ToolName" 685 | ToolName.Text = "" 686 | ToolName.TextColor3 = TEXT_COLOR 687 | ToolName.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY 688 | ToolName.TextStrokeColor3 = TEXT_STROKE_COLOR 689 | ToolName.FontFace = Font.new(FONT_FAMILY.Family, Enum.FontWeight.Medium, Enum.FontStyle.Normal) 690 | ToolName.TextSize = FONT_SIZE 691 | ToolName.Size = UDim2.new(1, -SLOT_EQUIP_THICKNESS * 2, 1, -SLOT_EQUIP_THICKNESS * 2) 692 | ToolName.Position = UDim2.fromScale(0.5, 0.5) 693 | ToolName.AnchorPoint = Vector2.new(0.5, 0.5) 694 | ToolName.TextWrapped = true 695 | ToolName.TextTruncate = Enum.TextTruncate.AtEnd 696 | ToolName.Parent = SlotFrame 697 | 698 | slot.Frame.LayoutOrder = slot.Index 699 | 700 | if index <= NumberOfHotbarSlots then -- Hotbar-Specific Slot Stuff 701 | -- ToolTip stuff 702 | ToolTip = Instance.new("TextLabel") 703 | ToolTip.Name = "ToolTip" 704 | ToolTip.Text = "" 705 | ToolTip.Size = UDim2.fromScale(1, 1) 706 | ToolTip.TextColor3 = TEXT_COLOR 707 | ToolTip.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY 708 | ToolTip.TextStrokeColor3 = TEXT_STROKE_COLOR 709 | ToolTip.FontFace = Font.new(FONT_FAMILY.Family, Enum.FontWeight.Medium, Enum.FontStyle.Normal) 710 | ToolTip.TextSize = FONT_SIZE 711 | ToolTip.ZIndex = 2 712 | ToolTip.TextWrapped = false 713 | ToolTip.TextYAlignment = Enum.TextYAlignment.Center 714 | ToolTip.BackgroundColor3 = TOOLTIP_BACKGROUND_COLOR 715 | ToolTip.BackgroundTransparency = SLOT_LOCKED_TRANSPARENCY 716 | ToolTip.AnchorPoint = Vector2.new(0.5, 1) 717 | ToolTip.BorderSizePixel = 0 718 | ToolTip.Visible = false 719 | ToolTip.AutomaticSize = Enum.AutomaticSize.X 720 | ToolTip.Parent = SlotFrame 721 | 722 | local ToolTipCorner: UICorner = Instance.new("UICorner") 723 | ToolTipCorner.Name = "Corner" 724 | ToolTipCorner.CornerRadius = TOOLTIP_CORNER_RADIUS 725 | ToolTipCorner.Parent = ToolTip 726 | 727 | local ToolTipPadding: UIPadding = Instance.new("UIPadding") 728 | ToolTipPadding.PaddingLeft = UDim.new(0, TOOLTIP_PADDING) 729 | ToolTipPadding.PaddingRight = UDim.new(0, TOOLTIP_PADDING) 730 | ToolTipPadding.PaddingTop = UDim.new(0, TOOLTIP_PADDING) 731 | ToolTipPadding.PaddingBottom = UDim.new(0, TOOLTIP_PADDING) 732 | ToolTipPadding.Parent = ToolTip 733 | SlotFrame.MouseEnter:Connect(function(): () 734 | if ToolTip.Text ~= "" then 735 | ToolTip.Visible = true 736 | end 737 | end) 738 | SlotFrame.MouseLeave:Connect(function(): () 739 | ToolTip.Visible = false 740 | end) 741 | 742 | function slot:MoveToInventory(): ...any 743 | if slot.Index <= NumberOfHotbarSlots then -- From a Hotbar slot 744 | local tool: any = slot.Tool 745 | self:Clear() --NOTE: Order matters here 746 | local newSlot: any = MakeSlot(UIGridFrame) 747 | newSlot:Fill(tool) 748 | if IsEquipped(tool) then -- Also unequip it --NOTE: HopperBin 749 | UnequipAllTools() 750 | end 751 | -- Also hide the inventory slot if we're showing results right now 752 | if ViewingSearchResults then 753 | newSlot.Frame.Visible = false 754 | newSlot.Parent = InventoryFrame 755 | end 756 | end 757 | end 758 | 759 | -- Show label and assign hotkeys for 1-9 and 0 (zero is always last slot when > 10 total) 760 | if index < 10 or index == NumberOfHotbarSlots then -- NOTE: Hardcoded on purpose! 761 | local slotNum: number = (index < 10) and index or 0 762 | SlotNumber = Instance.new("TextLabel") 763 | SlotNumber.BackgroundTransparency = 1 764 | SlotNumber.Name = "Number" 765 | SlotNumber.TextColor3 = TEXT_COLOR 766 | SlotNumber.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY 767 | SlotNumber.TextStrokeColor3 = TEXT_STROKE_COLOR 768 | SlotNumber.TextSize = FONT_SIZE 769 | SlotNumber.Text = tostring(slotNum) 770 | SlotNumber.FontFace = Font.new(FONT_FAMILY.Family, Enum.FontWeight.Heavy, Enum.FontStyle.Normal) 771 | SlotNumber.Size = UDim2.fromScale(0.4, 0.4) 772 | SlotNumber.Visible = false 773 | SlotNumber.Parent = SlotFrame 774 | HotkeyFns[ZERO_KEY_VALUE + slotNum] = slot.Select 775 | end 776 | end 777 | 778 | do -- Dragging Logic 779 | local startPoint: UDim2 = SlotFrame.Position 780 | local lastUpTime: number = 0 781 | local startParent: any = nil 782 | 783 | SlotFrame.DragBegin:Connect(function(dragPoint: UDim2): () 784 | Dragging[SlotFrame] = true 785 | startPoint = dragPoint 786 | 787 | SlotFrame.BorderSizePixel = 2 788 | inventoryIcon:lock() 789 | 790 | -- Raise above other slots 791 | SlotFrame.ZIndex = 2 792 | ToolIcon.ZIndex = 2 793 | ToolName.ZIndex = 2 794 | SlotFrame.Parent.ZIndex = 2 795 | if SlotNumber then 796 | SlotNumber.ZIndex = 2 797 | end 798 | -- if HighlightFrame then 799 | -- HighlightFrame.ZIndex = 2 800 | -- for _, child in pairs(HighlightFrame:GetChildren()) do 801 | -- child.ZIndex = 2 802 | -- end 803 | -- end 804 | 805 | -- Circumvent the ScrollingFrame's ClipsDescendants property 806 | startParent = SlotFrame.Parent 807 | if startParent == UIGridFrame then 808 | local newPosition: UDim2 = UDim2.new( 809 | 0, 810 | SlotFrame.AbsolutePosition.X - InventoryFrame.AbsolutePosition.X, 811 | 0, 812 | SlotFrame.AbsolutePosition.Y - InventoryFrame.AbsolutePosition.Y 813 | ) 814 | SlotFrame.Parent = InventoryFrame 815 | SlotFrame.Position = newPosition 816 | 817 | FakeSlotFrame = Instance.new("Frame") 818 | FakeSlotFrame.Name = "FakeSlot" 819 | FakeSlotFrame.LayoutOrder = SlotFrame.LayoutOrder 820 | FakeSlotFrame.Size = SlotFrame.Size 821 | FakeSlotFrame.BackgroundTransparency = 1 822 | FakeSlotFrame.Parent = UIGridFrame 823 | end 824 | end) 825 | 826 | SlotFrame.DragStopped:Connect(function(x: number, y: number): () 827 | if FakeSlotFrame then 828 | FakeSlotFrame:Destroy() 829 | end 830 | 831 | local now: number = os.clock() 832 | 833 | SlotFrame.Position = startPoint 834 | SlotFrame.Parent = startParent 835 | 836 | SlotFrame.BorderSizePixel = 0 837 | inventoryIcon:unlock() 838 | 839 | -- Restore height 840 | SlotFrame.ZIndex = 1 841 | ToolIcon.ZIndex = 1 842 | ToolName.ZIndex = 1 843 | startParent.ZIndex = 1 844 | 845 | if SlotNumber then 846 | SlotNumber.ZIndex = 1 847 | end 848 | -- if HighlightFrame then 849 | -- HighlightFrame.ZIndex = 1 850 | -- for _, child in pairs(HighlightFrame:GetChildren()) do 851 | -- child.ZIndex = 1 852 | -- end 853 | -- end 854 | 855 | Dragging[SlotFrame] = nil 856 | 857 | -- Make sure the tool wasn't dropped 858 | if not slot.Tool then 859 | return 860 | end 861 | 862 | -- Check where we were dropped 863 | if CheckBounds(InventoryFrame, x, y) then 864 | if slot.Index <= NumberOfHotbarSlots then 865 | slot:MoveToInventory() 866 | end 867 | -- Check for double clicking on an inventory slot, to move into empty hotbar slot 868 | if slot.Index > NumberOfHotbarSlots and now - lastUpTime < DOUBLE_CLICK_TIME then 869 | if LowestEmptySlot then 870 | local myTool: any = slot.Tool 871 | slot:Clear() 872 | LowestEmptySlot:Fill(myTool) 873 | slot:Delete() 874 | end 875 | now = 0 -- Resets the timer 876 | end 877 | elseif CheckBounds(HotbarFrame, x, y) then 878 | local closest: { number } = { math.huge, nil :: any } 879 | for i: number = 1, NumberOfHotbarSlots do 880 | local otherSlot: any = Slots[i] 881 | local offset: number = GetOffset(otherSlot.Frame, Vector2.new(x, y)) 882 | if offset < closest[1] then 883 | closest = { offset, otherSlot } 884 | end 885 | end 886 | local closestSlot: any = closest[2] 887 | if closestSlot ~= slot then 888 | slot:Swap(closestSlot) 889 | if slot.Index > NumberOfHotbarSlots then 890 | local tool: Tool = slot.Tool 891 | if not tool then -- Clean up after ourselves if we're an inventory slot that's now empty 892 | slot:Delete() 893 | else -- Moved inventory slot to hotbar slot, and gained a tool that needs to be unequipped 894 | if IsEquipped(tool) then --NOTE: HopperBin 895 | UnequipAllTools() 896 | end 897 | -- Also hide the inventory slot if we're showing results right now 898 | if ViewingSearchResults then 899 | slot.Frame.Visible = false 900 | slot.Frame.Parent = InventoryFrame 901 | end 902 | end 903 | end 904 | end 905 | else 906 | -- local tool = slot.Tool 907 | -- if tool.CanBeDropped then --TODO: HopperBins 908 | -- tool.Parent = workspace 909 | -- --TODO: Move away from character 910 | -- end 911 | if slot.Index <= NumberOfHotbarSlots then 912 | slot:MoveToInventory() --NOTE: Temporary 913 | end 914 | end 915 | 916 | lastUpTime = now 917 | end) 918 | end 919 | 920 | -- All ready! 921 | SlotFrame.Parent = parent 922 | Slots[index] = slot 923 | 924 | if index > NumberOfHotbarSlots then 925 | UpdateScrollingFrameCanvasSize() 926 | -- Scroll to new inventory slot, if we're open and not viewing search results 927 | if InventoryFrame.Visible and not ViewingSearchResults then 928 | local offset: number = ScrollingFrame.CanvasSize.Y.Offset - ScrollingFrame.AbsoluteSize.Y 929 | ScrollingFrame.CanvasPosition = Vector2.new(0, math.max(0, offset)) 930 | end 931 | end 932 | 933 | return slot 934 | end 935 | 936 | local function OnChildAdded(child: Instance): () -- To Character or Backpack 937 | if not child:IsA("Tool") and not child:IsA("HopperBin") then --NOTE: HopperBin 938 | if child:IsA("Humanoid") and child.Parent == Character then 939 | Humanoid = child 940 | end 941 | return 942 | end 943 | local tool: any = child 944 | 945 | if tool.Parent == Character then 946 | ShowVRBackpackPopup() 947 | end 948 | 949 | if ActiveHopper and tool.Parent == Character then --NOTE: HopperBin 950 | DisableActiveHopper() 951 | end 952 | 953 | --TODO: Optimize / refactor / do something else 954 | if not StarterToolFound and tool.Parent == Character and not SlotsByTool[tool] then 955 | local starterGear: Instance? = Player:FindFirstChild("StarterGear") 956 | if starterGear then 957 | if starterGear:FindFirstChild(tool.Name) then 958 | StarterToolFound = true 959 | local slot: any = LowestEmptySlot or MakeSlot(UIGridFrame) 960 | for i: number = slot.Index, 1, -1 do 961 | local curr = Slots[i] -- An empty slot, because above 962 | local pIndex: number = i - 1 963 | if pIndex > 0 then 964 | local prev = Slots[pIndex] -- Guaranteed to be full, because above 965 | prev:Swap(curr) 966 | else 967 | curr:Fill(tool) 968 | end 969 | end 970 | -- Have to manually unequip a possibly equipped tool 971 | for _, children: Instance in pairs(Character:GetChildren()) do 972 | if children:IsA("Tool") and children ~= tool then 973 | children.Parent = Backpack 974 | end 975 | end 976 | AdjustHotbarFrames() 977 | return -- We're done here 978 | end 979 | end 980 | end 981 | 982 | -- The tool is either moving or new 983 | local slot: any = SlotsByTool[tool] 984 | if slot then 985 | slot:UpdateEquipView() 986 | else -- New! Put into lowest hotbar slot or new inventory slot 987 | slot = LowestEmptySlot or MakeSlot(UIGridFrame) 988 | slot:Fill(tool) 989 | if slot.Index <= NumberOfHotbarSlots and not InventoryFrame.Visible then 990 | AdjustHotbarFrames() 991 | end 992 | if tool:IsA("HopperBin") then --NOTE: HopperBin 993 | if tool.Active then 994 | UnequipAllTools() 995 | ActiveHopper = tool 996 | end 997 | end 998 | end 999 | 1000 | BackpackScript.BackpackItemAdded:Fire(slot) 1001 | end 1002 | 1003 | local function OnChildRemoved(child: Instance): () -- From Character or Backpack 1004 | if not child:IsA("Tool") and not child:IsA("HopperBin") then --NOTE: HopperBin 1005 | return 1006 | end 1007 | local tool: Tool | any = child 1008 | 1009 | ShowVRBackpackPopup() 1010 | 1011 | -- Ignore this event if we're just moving between the two 1012 | local newParent: any = tool.Parent 1013 | if newParent == Character or newParent == Backpack then 1014 | return 1015 | end 1016 | 1017 | local slot: any = SlotsByTool[tool] 1018 | if slot then 1019 | slot:Clear() 1020 | if slot.Index > NumberOfHotbarSlots then -- Inventory slot 1021 | slot:Delete() 1022 | elseif not InventoryFrame.Visible then 1023 | AdjustHotbarFrames() 1024 | end 1025 | end 1026 | 1027 | if tool :: any == ActiveHopper then --NOTE: HopperBin 1028 | ActiveHopper = nil :: any 1029 | end 1030 | 1031 | if slot then 1032 | BackpackScript.BackpackItemRemoved:Fire(slot) 1033 | end 1034 | if isInventoryEmpty() then 1035 | BackpackScript.BackpackEmpty:Fire() 1036 | end 1037 | end 1038 | 1039 | local function OnCharacterAdded(character: Model): () 1040 | -- First, clean up any old slots 1041 | for i: number = #Slots, 1, -1 do 1042 | local slot = Slots[i] 1043 | if slot.Tool then 1044 | slot:Clear() 1045 | end 1046 | if i > NumberOfHotbarSlots then 1047 | slot:Delete() 1048 | end 1049 | end 1050 | ActiveHopper = nil :: any --NOTE: HopperBin 1051 | 1052 | -- And any old Connections 1053 | for _, conn: RBXScriptConnection in pairs(CharConns) do 1054 | conn:Disconnect() 1055 | end 1056 | CharConns = {} 1057 | 1058 | -- Hook up the new character 1059 | Character = character 1060 | table.insert(CharConns, character.ChildRemoved:Connect(OnChildRemoved)) 1061 | table.insert(CharConns, character.ChildAdded:Connect(OnChildAdded)) 1062 | for _, child: Instance in pairs(character:GetChildren()) do 1063 | OnChildAdded(child) 1064 | end 1065 | --NOTE: Humanoid is set inside OnChildAdded 1066 | 1067 | -- And the new backpack, when it gets here 1068 | Backpack = Player:WaitForChild("Backpack") 1069 | table.insert(CharConns, Backpack.ChildRemoved:Connect(OnChildRemoved)) 1070 | table.insert(CharConns, Backpack.ChildAdded:Connect(OnChildAdded)) 1071 | for _, child: Instance in pairs(Backpack:GetChildren()) do 1072 | OnChildAdded(child) 1073 | end 1074 | 1075 | AdjustHotbarFrames() 1076 | end 1077 | 1078 | local function OnInputBegan(input: InputObject, isProcessed: boolean): () 1079 | local ChatInputBarConfiguration = 1080 | TextChatService:FindFirstChildOfClass("ChatInputBarConfiguration") :: ChatInputBarConfiguration 1081 | -- Pass through keyboard hotkeys when not typing into a TextBox and not disabled (except for the Drop key) 1082 | if 1083 | input.UserInputType == Enum.UserInputType.Keyboard 1084 | and not TextBoxFocused 1085 | and not ChatInputBarConfiguration.IsFocused 1086 | and (WholeThingEnabled or input.KeyCode.Value == DROP_HOTKEY_VALUE) 1087 | then 1088 | local hotkeyBehavior: any = HotkeyFns[input.KeyCode.Value] 1089 | if hotkeyBehavior then 1090 | hotkeyBehavior(isProcessed) 1091 | end 1092 | end 1093 | 1094 | local inputType: Enum.UserInputType = input.UserInputType 1095 | if not isProcessed then 1096 | if inputType == Enum.UserInputType.MouseButton1 or inputType == Enum.UserInputType.Touch then 1097 | if InventoryFrame.Visible then 1098 | inventoryIcon:deselect() 1099 | end 1100 | end 1101 | end 1102 | end 1103 | 1104 | local function OnUISChanged(): () 1105 | -- Detect if player is using Touch 1106 | if UserInputService:GetLastInputType() == Enum.UserInputType.Touch then 1107 | for i: number = 1, NumberOfHotbarSlots do 1108 | Slots[i]:TurnNumber(false) 1109 | end 1110 | return 1111 | end 1112 | 1113 | -- Detect if player is using Keyboard 1114 | if UserInputService:GetLastInputType() == Enum.UserInputType.Keyboard then 1115 | for i: number = 1, NumberOfHotbarSlots do 1116 | Slots[i]:TurnNumber(true) 1117 | end 1118 | return 1119 | end 1120 | 1121 | -- Detect if player is using Mouse 1122 | for _, mouse: any in pairs(MOUSE_INPUT_TYPES) do 1123 | if UserInputService:GetLastInputType() == mouse then 1124 | for i: number = 1, NumberOfHotbarSlots do 1125 | Slots[i]:TurnNumber(true) 1126 | end 1127 | return 1128 | end 1129 | end 1130 | 1131 | -- Detect if player is using Controller 1132 | for _, gamepad: any in pairs(GAMEPAD_INPUT_TYPES) do 1133 | if UserInputService:GetLastInputType() == gamepad then 1134 | for i: number = 1, NumberOfHotbarSlots do 1135 | Slots[i]:TurnNumber(false) 1136 | end 1137 | return 1138 | end 1139 | end 1140 | end 1141 | 1142 | local lastChangeToolInputObject: InputObject = nil 1143 | local lastChangeToolInputTime: number = nil 1144 | local maxEquipDeltaTime: number = 0.06 1145 | local noOpFunc = function() end 1146 | -- local selectDirection = Vector2.new(0, 0) 1147 | 1148 | function unbindAllGamepadEquipActions(): () 1149 | ContextActionService:UnbindAction("BackpackHasGamepadFocus") 1150 | ContextActionService:UnbindAction("BackpackCloseInventory") 1151 | end 1152 | 1153 | -- local function setHotbarVisibility(visible: boolean, isInventoryScreen: boolean) 1154 | -- for i: number = 1, NumberOfHotbarSlots do 1155 | -- local hotbarSlot = Slots[i] 1156 | -- if hotbarSlot and hotbarSlot.Frame and (isInventoryScreen or hotbarSlot.Tool) then 1157 | -- hotbarSlot.Frame.Visible = visible 1158 | -- end 1159 | -- end 1160 | -- end 1161 | 1162 | -- local function getInputDirection(inputObject: InputObject): Vector2 1163 | -- local buttonModifier = 1 1164 | -- if inputObject.UserInputState == Enum.UserInputState.End then 1165 | -- buttonModifier = -1 1166 | -- end 1167 | 1168 | -- if inputObject.KeyCode == Enum.KeyCode.Thumbstick1 then 1169 | -- local Magnitude = inputObject.Position.Magnitude 1170 | 1171 | -- if Magnitude > 0.98 then 1172 | -- local normalizedVector = 1173 | -- Vector2.new(inputObject.Position.X / Magnitude, -inputObject.Position.Y / Magnitude) 1174 | -- selectDirection = normalizedVector 1175 | -- else 1176 | -- selectDirection = Vector2.new(0, 0) 1177 | -- end 1178 | -- elseif inputObject.KeyCode == Enum.KeyCode.DPadLeft then 1179 | -- selectDirection = Vector2.new(selectDirection.X - 1 * buttonModifier, selectDirection.Y) 1180 | -- elseif inputObject.KeyCode == Enum.KeyCode.DPadRight then 1181 | -- selectDirection = Vector2.new(selectDirection.X + 1 * buttonModifier, selectDirection.Y) 1182 | -- elseif inputObject.KeyCode == Enum.KeyCode.DPadUp then 1183 | -- selectDirection = Vector2.new(selectDirection.X, selectDirection.Y - 1 * buttonModifier) 1184 | -- elseif inputObject.KeyCode == Enum.KeyCode.DPadDown then 1185 | -- selectDirection = Vector2.new(selectDirection.X, selectDirection.Y + 1 * buttonModifier) 1186 | -- else 1187 | -- selectDirection = Vector2.new(0, 0) 1188 | -- end 1189 | 1190 | -- return selectDirection 1191 | -- end 1192 | 1193 | -- local selectToolExperiment = function(actionName: string, inputState: Enum.UserInputState, inputObject: InputObject) 1194 | -- local inputDirection = getInputDirection(inputObject) 1195 | 1196 | -- if inputDirection == Vector2.new(0, 0) then 1197 | -- return 1198 | -- end 1199 | 1200 | -- local angle = math.atan2(inputDirection.Y, inputDirection.X) - math.atan2(-1, 0) 1201 | -- if angle < 0 then 1202 | -- angle = angle + (math.pi * 2) 1203 | -- end 1204 | 1205 | -- local quarterPi = (math.pi * 0.25) 1206 | 1207 | -- local index = (angle / quarterPi) + 1 1208 | -- index = math.floor(index + 0.5) -- round index to whole number 1209 | -- if index > NumberOfHotbarSlots then 1210 | -- index = 1 1211 | -- end 1212 | 1213 | -- if index > 0 then 1214 | -- local selectedSlot = Slots[index] 1215 | -- if selectedSlot and selectedSlot.Tool and not selectedSlot:IsEquipped() then 1216 | -- selectedSlot:Select() 1217 | -- end 1218 | -- else 1219 | -- UnequipAllTools() 1220 | -- end 1221 | -- end 1222 | 1223 | -- selene: allow(unused_variable) 1224 | changeToolFunc = function(actionName: string, inputState: Enum.UserInputState, inputObject: InputObject): () 1225 | if inputState ~= Enum.UserInputState.Begin then 1226 | return 1227 | end 1228 | 1229 | if lastChangeToolInputObject then 1230 | if 1231 | ( 1232 | lastChangeToolInputObject.KeyCode == Enum.KeyCode.ButtonR1 1233 | and inputObject.KeyCode == Enum.KeyCode.ButtonL1 1234 | ) 1235 | or ( 1236 | lastChangeToolInputObject.KeyCode == Enum.KeyCode.ButtonL1 1237 | and inputObject.KeyCode == Enum.KeyCode.ButtonR1 1238 | ) 1239 | then 1240 | if (os.clock() - lastChangeToolInputTime) <= maxEquipDeltaTime then 1241 | UnequipAllTools() 1242 | lastChangeToolInputObject = inputObject 1243 | lastChangeToolInputTime = os.clock() 1244 | return 1245 | end 1246 | end 1247 | end 1248 | 1249 | lastChangeToolInputObject = inputObject 1250 | lastChangeToolInputTime = os.clock() 1251 | 1252 | task.delay(maxEquipDeltaTime, function(): () 1253 | if lastChangeToolInputObject ~= inputObject then 1254 | return 1255 | end 1256 | 1257 | local moveDirection: number = 0 1258 | if inputObject.KeyCode == Enum.KeyCode.ButtonL1 then 1259 | moveDirection = -1 1260 | else 1261 | moveDirection = 1 1262 | end 1263 | 1264 | for i: number = 1, NumberOfHotbarSlots do 1265 | local hotbarSlot: any = Slots[i] 1266 | if hotbarSlot:IsEquipped() then 1267 | local newSlotPosition: number = moveDirection + i 1268 | local hitEdge: boolean = false 1269 | if newSlotPosition > NumberOfHotbarSlots then 1270 | newSlotPosition = 1 1271 | hitEdge = true 1272 | elseif newSlotPosition < 1 then 1273 | newSlotPosition = NumberOfHotbarSlots 1274 | hitEdge = true 1275 | end 1276 | 1277 | local origNewSlotPos: number = newSlotPosition 1278 | while not Slots[newSlotPosition].Tool do 1279 | newSlotPosition = newSlotPosition + moveDirection 1280 | if newSlotPosition == origNewSlotPos then 1281 | return 1282 | end 1283 | 1284 | if newSlotPosition > NumberOfHotbarSlots then 1285 | newSlotPosition = 1 1286 | hitEdge = true 1287 | elseif newSlotPosition < 1 then 1288 | newSlotPosition = NumberOfHotbarSlots 1289 | hitEdge = true 1290 | end 1291 | end 1292 | 1293 | if hitEdge then 1294 | UnequipAllTools() 1295 | lastEquippedSlot = nil 1296 | else 1297 | Slots[newSlotPosition]:Select() 1298 | end 1299 | return 1300 | end 1301 | end 1302 | 1303 | if lastEquippedSlot and lastEquippedSlot.Tool then 1304 | lastEquippedSlot:Select() 1305 | return 1306 | end 1307 | 1308 | local startIndex: number = moveDirection == -1 and NumberOfHotbarSlots or 1 1309 | local endIndex: number = moveDirection == -1 and 1 or NumberOfHotbarSlots 1310 | for i: number = startIndex, endIndex, moveDirection do 1311 | if Slots[i].Tool then 1312 | Slots[i]:Select() 1313 | return 1314 | end 1315 | end 1316 | end) 1317 | end 1318 | 1319 | function getGamepadSwapSlot(): any 1320 | for i: number = 1, #Slots do 1321 | if Slots[i].Frame.BorderSizePixel > 0 then 1322 | return Slots[i] 1323 | end 1324 | end 1325 | return 1326 | end 1327 | 1328 | -- selene: allow(unused_variable) 1329 | function changeSlot(slot: any): () 1330 | local swapInVr: boolean = not VRService.VREnabled or InventoryFrame.Visible 1331 | 1332 | if slot.Frame == GuiService.SelectedObject and swapInVr then 1333 | local currentlySelectedSlot: any = getGamepadSwapSlot() 1334 | 1335 | if currentlySelectedSlot then 1336 | currentlySelectedSlot.Frame.BorderSizePixel = 0 1337 | if currentlySelectedSlot ~= slot then 1338 | slot:Swap(currentlySelectedSlot) 1339 | VRInventorySelector.SelectionImageObject.Visible = false 1340 | 1341 | if slot.Index > NumberOfHotbarSlots and not slot.Tool then 1342 | if GuiService.SelectedObject == slot.Frame then 1343 | GuiService.SelectedObject = currentlySelectedSlot.Frame 1344 | end 1345 | slot:Delete() 1346 | end 1347 | 1348 | if currentlySelectedSlot.Index > NumberOfHotbarSlots and not currentlySelectedSlot.Tool then 1349 | if GuiService.SelectedObject == currentlySelectedSlot.Frame then 1350 | GuiService.SelectedObject = slot.Frame 1351 | end 1352 | currentlySelectedSlot:Delete() 1353 | end 1354 | end 1355 | else 1356 | local startSize: UDim2 = slot.Frame.Size 1357 | local startPosition: UDim2 = slot.Frame.Position 1358 | slot.Frame:TweenSizeAndPosition( 1359 | startSize + UDim2.fromOffset(10, 10), 1360 | startPosition - UDim2.fromOffset(5, 5), 1361 | Enum.EasingDirection.Out, 1362 | Enum.EasingStyle.Quad, 1363 | 0.1, 1364 | true, 1365 | function(): () 1366 | slot.Frame:TweenSizeAndPosition( 1367 | startSize, 1368 | startPosition, 1369 | Enum.EasingDirection.In, 1370 | Enum.EasingStyle.Quad, 1371 | 0.1, 1372 | true 1373 | ) 1374 | end 1375 | ) 1376 | slot.Frame.BorderSizePixel = 3 1377 | VRInventorySelector.SelectionImageObject.Visible = true 1378 | end 1379 | else 1380 | slot:Select() 1381 | VRInventorySelector.SelectionImageObject.Visible = false 1382 | end 1383 | end 1384 | 1385 | function vrMoveSlotToInventory(): () 1386 | if not VRService.VREnabled then 1387 | return 1388 | end 1389 | 1390 | local currentlySelectedSlot: any = getGamepadSwapSlot() 1391 | if currentlySelectedSlot and currentlySelectedSlot.Tool then 1392 | currentlySelectedSlot.Frame.BorderSizePixel = 0 1393 | currentlySelectedSlot:MoveToInventory() 1394 | VRInventorySelector.SelectionImageObject.Visible = false 1395 | end 1396 | end 1397 | 1398 | function enableGamepadInventoryControl(): () 1399 | local goBackOneLevel = function(): () 1400 | -- if inputState ~= Enum.UserInputState.Begin then 1401 | -- return 1402 | -- end 1403 | 1404 | local selectedSlot: any = getGamepadSwapSlot() 1405 | if selectedSlot then 1406 | -- selene: allow(shadowing) 1407 | local selectedSlot: any = getGamepadSwapSlot() 1408 | if selectedSlot then 1409 | selectedSlot.Frame.BorderSizePixel = 0 1410 | return 1411 | end 1412 | elseif InventoryFrame.Visible then 1413 | inventoryIcon:deselect() 1414 | end 1415 | end 1416 | 1417 | ContextActionService:BindAction("BackpackHasGamepadFocus", noOpFunc, false, Enum.UserInputType.Gamepad1) 1418 | ContextActionService:BindAction( 1419 | "BackpackCloseInventory", 1420 | goBackOneLevel, 1421 | false, 1422 | Enum.KeyCode.ButtonB, 1423 | Enum.KeyCode.ButtonStart 1424 | ) 1425 | 1426 | -- Gaze select will automatically select the object for us! 1427 | if not UseGazeSelection() then 1428 | GuiService.SelectedObject = HotbarFrame:FindFirstChild("1") 1429 | end 1430 | end 1431 | 1432 | function disableGamepadInventoryControl(): () 1433 | unbindAllGamepadEquipActions() 1434 | 1435 | for i: number = 1, NumberOfHotbarSlots do 1436 | local hotbarSlot: any = Slots[i] 1437 | if hotbarSlot and hotbarSlot.Frame then 1438 | hotbarSlot.Frame.BorderSizePixel = 0 1439 | end 1440 | end 1441 | 1442 | if GuiService.SelectedObject and GuiService.SelectedObject:IsDescendantOf(MainFrame) then 1443 | GuiService.SelectedObject = nil 1444 | end 1445 | end 1446 | 1447 | local function bindBackpackHotbarAction(): () 1448 | if WholeThingEnabled and not GamepadActionsBound then 1449 | GamepadActionsBound = true 1450 | ContextActionService:BindAction( 1451 | "BackpackHotbarEquip", 1452 | changeToolFunc, 1453 | false, 1454 | Enum.KeyCode.ButtonL1, 1455 | Enum.KeyCode.ButtonR1 1456 | ) 1457 | end 1458 | end 1459 | 1460 | local function unbindBackpackHotbarAction(): () 1461 | disableGamepadInventoryControl() 1462 | GamepadActionsBound = false 1463 | ContextActionService:UnbindAction("BackpackHotbarEquip") 1464 | end 1465 | 1466 | function gamepadDisconnected(): () 1467 | GamepadEnabled = false 1468 | disableGamepadInventoryControl() 1469 | end 1470 | 1471 | function gamepadConnected(): () 1472 | GamepadEnabled = true 1473 | GuiService:AddSelectionParent("BackpackSelection", MainFrame) 1474 | 1475 | if FullHotbarSlots >= 1 then 1476 | bindBackpackHotbarAction() 1477 | end 1478 | 1479 | if InventoryFrame.Visible then 1480 | enableGamepadInventoryControl() 1481 | end 1482 | end 1483 | 1484 | local function OnIconChanged(enabled: boolean): () 1485 | -- Check for enabling/disabling the whole thing 1486 | local success, _topbarEnabled = pcall(function() 1487 | return enabled and StarterGui:GetCore("TopbarEnabled") 1488 | end) 1489 | 1490 | if not success then 1491 | return 1492 | end 1493 | 1494 | WholeThingEnabled = enabled 1495 | MainFrame.Visible = enabled 1496 | 1497 | -- Eat/Release hotkeys (Doesn't affect UserInputService) 1498 | -- for _, keyString in pairs(HotkeyStrings) do 1499 | -- if enabled then 1500 | -- GuiService:AddKey(keyString) 1501 | -- else 1502 | -- GuiService:RemoveKey(keyString) 1503 | -- end 1504 | -- end 1505 | 1506 | if enabled then 1507 | if FullHotbarSlots >= 1 then 1508 | bindBackpackHotbarAction() 1509 | end 1510 | else 1511 | unbindBackpackHotbarAction() 1512 | end 1513 | end 1514 | 1515 | local function MakeVRRoundButton(name: string, image: string): (ImageButton, ImageLabel, ImageLabel) 1516 | local newButton: ImageButton = Instance.new("ImageButton") 1517 | newButton.BackgroundTransparency = 1 1518 | newButton.Name = name 1519 | newButton.Size = UDim2.fromOffset(40, 40) 1520 | newButton.Image = "rbxasset://textures/ui/Keyboard/close_button_background.png" 1521 | 1522 | local buttonIcon: ImageLabel = Instance.new("ImageLabel") 1523 | buttonIcon.Name = "Icon" 1524 | buttonIcon.BackgroundTransparency = 1 1525 | buttonIcon.Size = UDim2.fromScale(0.5, 0.5) 1526 | buttonIcon.Position = UDim2.fromScale(0.25, 0.25) 1527 | buttonIcon.Image = image 1528 | buttonIcon.Parent = newButton 1529 | 1530 | local buttonSelectionObject: ImageLabel = Instance.new("ImageLabel") 1531 | buttonSelectionObject.BackgroundTransparency = 1 1532 | buttonSelectionObject.Name = "Selection" 1533 | buttonSelectionObject.Size = UDim2.fromScale(0.9, 0.9) 1534 | buttonSelectionObject.Position = UDim2.fromScale(0.05, 0.05) 1535 | buttonSelectionObject.Image = "rbxasset://textures/ui/Keyboard/close_button_selection.png" 1536 | newButton.SelectionImageObject = buttonSelectionObject 1537 | 1538 | return newButton, buttonIcon, buttonSelectionObject 1539 | end 1540 | 1541 | -- Make the main frame, which (mostly) covers the screen 1542 | MainFrame = Instance.new("Frame") 1543 | MainFrame.BackgroundTransparency = 1 1544 | MainFrame.Name = "Backpack" 1545 | MainFrame.Size = UDim2.fromScale(1, 1) 1546 | MainFrame.Visible = false 1547 | MainFrame.Parent = BackpackGui 1548 | 1549 | -- Make the HotbarFrame, which holds only the Hotbar Slots 1550 | HotbarFrame = Instance.new("Frame") 1551 | HotbarFrame.BackgroundTransparency = 1 1552 | HotbarFrame.Name = "Hotbar" 1553 | HotbarFrame.Size = UDim2.fromScale(1, 1) 1554 | HotbarFrame.Parent = MainFrame 1555 | 1556 | -- Make all the Hotbar Slots 1557 | for index: number = 1, NumberOfHotbarSlots do 1558 | local slot: any = MakeSlot(HotbarFrame, index) 1559 | slot.Frame.Visible = false 1560 | 1561 | if not LowestEmptySlot then 1562 | LowestEmptySlot = slot 1563 | end 1564 | end 1565 | 1566 | local LeftBumperButton: ImageLabel = Instance.new("ImageLabel") 1567 | LeftBumperButton.BackgroundTransparency = 1 1568 | LeftBumperButton.Name = "LeftBumper" 1569 | LeftBumperButton.Size = UDim2.fromOffset(40, 40) 1570 | LeftBumperButton.Position = UDim2.new(0, -LeftBumperButton.Size.X.Offset, 0.5, -LeftBumperButton.Size.Y.Offset / 2) 1571 | 1572 | local RightBumperButton: ImageLabel = Instance.new("ImageLabel") 1573 | RightBumperButton.BackgroundTransparency = 1 1574 | RightBumperButton.Name = "RightBumper" 1575 | RightBumperButton.Size = UDim2.fromOffset(40, 40) 1576 | RightBumperButton.Position = UDim2.new(1, 0, 0.5, -RightBumperButton.Size.Y.Offset / 2) 1577 | 1578 | -- Make the Inventory, which holds the ScrollingFrame, the header, and the search box 1579 | InventoryFrame = Instance.new("Frame") 1580 | InventoryFrame.Name = "Inventory" 1581 | InventoryFrame.Size = UDim2.fromScale(1, 1) 1582 | InventoryFrame.BackgroundTransparency = BACKGROUND_TRANSPARENCY 1583 | InventoryFrame.BackgroundColor3 = BACKGROUND_COLOR 1584 | InventoryFrame.Active = true 1585 | InventoryFrame.Visible = false 1586 | InventoryFrame.Parent = MainFrame 1587 | 1588 | -- Add corners to the InventoryFrame 1589 | local corner: UICorner = Instance.new("UICorner") 1590 | corner.Name = "Corner" 1591 | corner.CornerRadius = BACKGROUND_CORNER_RADIUS 1592 | corner.Parent = InventoryFrame 1593 | 1594 | VRInventorySelector = Instance.new("TextButton") 1595 | VRInventorySelector.Name = "VRInventorySelector" 1596 | VRInventorySelector.Position = UDim2.new(0, 0, 0, 0) 1597 | VRInventorySelector.Size = UDim2.fromScale(1, 1) 1598 | VRInventorySelector.BackgroundTransparency = 1 1599 | VRInventorySelector.Text = "" 1600 | VRInventorySelector.Parent = InventoryFrame 1601 | 1602 | local selectorImage: ImageLabel = Instance.new("ImageLabel") 1603 | selectorImage.BackgroundTransparency = 1 1604 | selectorImage.Name = "Selector" 1605 | selectorImage.Size = UDim2.fromScale(1, 1) 1606 | selectorImage.Image = "rbxasset://textures/ui/Keyboard/key_selection_9slice.png" 1607 | selectorImage.ScaleType = Enum.ScaleType.Slice 1608 | selectorImage.SliceCenter = Rect.new(12, 12, 52, 52) 1609 | selectorImage.Visible = false 1610 | VRInventorySelector.SelectionImageObject = selectorImage 1611 | 1612 | VRInventorySelector.MouseButton1Click:Connect(function(): () 1613 | vrMoveSlotToInventory() 1614 | end) 1615 | 1616 | -- Make the ScrollingFrame, which holds the rest of the Slots (however many) 1617 | ScrollingFrame = Instance.new("ScrollingFrame") 1618 | ScrollingFrame.BackgroundTransparency = 1 1619 | ScrollingFrame.Name = "ScrollingFrame" 1620 | ScrollingFrame.Size = UDim2.fromScale(1, 1) 1621 | ScrollingFrame.Selectable = false 1622 | ScrollingFrame.ScrollingDirection = Enum.ScrollingDirection.Y 1623 | ScrollingFrame.BorderSizePixel = 0 1624 | ScrollingFrame.ScrollBarThickness = 8 1625 | ScrollingFrame.ScrollBarImageColor3 = Color3.new(1, 1, 1) 1626 | ScrollingFrame.VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar 1627 | ScrollingFrame.CanvasSize = UDim2.new(0, 0, 0, 0) 1628 | ScrollingFrame.Parent = InventoryFrame 1629 | 1630 | UIGridFrame = Instance.new("Frame") 1631 | UIGridFrame.BackgroundTransparency = 1 1632 | UIGridFrame.Name = "UIGridFrame" 1633 | UIGridFrame.Selectable = false 1634 | UIGridFrame.Size = UDim2.new(1, -(ICON_BUFFER_PIXELS * 2), 1, 0) 1635 | UIGridFrame.Position = UDim2.fromOffset(ICON_BUFFER_PIXELS, 0) 1636 | UIGridFrame.Parent = ScrollingFrame 1637 | 1638 | UIGridLayout = Instance.new("UIGridLayout") 1639 | UIGridLayout.SortOrder = Enum.SortOrder.LayoutOrder 1640 | UIGridLayout.CellSize = UDim2.fromOffset(ICON_SIZE_PIXELS, ICON_SIZE_PIXELS) 1641 | UIGridLayout.CellPadding = UDim2.fromOffset(ICON_BUFFER_PIXELS, ICON_BUFFER_PIXELS) 1642 | UIGridLayout.Parent = UIGridFrame 1643 | 1644 | ScrollUpInventoryButton = MakeVRRoundButton("ScrollUpButton", "rbxasset://textures/ui/Backpack/ScrollUpArrow.png") 1645 | ScrollUpInventoryButton.Size = UDim2.fromOffset(34, 34) 1646 | ScrollUpInventoryButton.Position = 1647 | UDim2.new(0.5, -ScrollUpInventoryButton.Size.X.Offset / 2, 0, INVENTORY_HEADER_SIZE + 3) 1648 | ScrollUpInventoryButton.Icon.Position = ScrollUpInventoryButton.Icon.Position - UDim2.fromOffset(0, 2) 1649 | ScrollUpInventoryButton.MouseButton1Click:Connect(function(): () 1650 | ScrollingFrame.CanvasPosition = Vector2.new( 1651 | ScrollingFrame.CanvasPosition.X, 1652 | Clamp( 1653 | 0, 1654 | ScrollingFrame.CanvasSize.Y.Offset - ScrollingFrame.AbsoluteWindowSize.Y, 1655 | ScrollingFrame.CanvasPosition.Y - (ICON_BUFFER_PIXELS + ICON_SIZE_PIXELS) 1656 | ) 1657 | ) 1658 | end) 1659 | 1660 | ScrollDownInventoryButton = MakeVRRoundButton("ScrollDownButton", "rbxasset://textures/ui/Backpack/ScrollUpArrow.png") 1661 | ScrollDownInventoryButton.Rotation = 180 1662 | ScrollDownInventoryButton.Icon.Position = ScrollDownInventoryButton.Icon.Position - UDim2.fromOffset(0, 2) 1663 | ScrollDownInventoryButton.Size = UDim2.fromOffset(34, 34) 1664 | ScrollDownInventoryButton.Position = 1665 | UDim2.new(0.5, -ScrollDownInventoryButton.Size.X.Offset / 2, 1, -ScrollDownInventoryButton.Size.Y.Offset - 3) 1666 | ScrollDownInventoryButton.MouseButton1Click:Connect(function(): () 1667 | ScrollingFrame.CanvasPosition = Vector2.new( 1668 | ScrollingFrame.CanvasPosition.X, 1669 | Clamp( 1670 | 0, 1671 | ScrollingFrame.CanvasSize.Y.Offset - ScrollingFrame.AbsoluteWindowSize.Y, 1672 | ScrollingFrame.CanvasPosition.Y + (ICON_BUFFER_PIXELS + ICON_SIZE_PIXELS) 1673 | ) 1674 | ) 1675 | end) 1676 | 1677 | ScrollingFrame.Changed:Connect(function(prop: string): () 1678 | if prop == "AbsoluteWindowSize" or prop == "CanvasPosition" or prop == "CanvasSize" then 1679 | local canScrollUp: boolean = ScrollingFrame.CanvasPosition.Y ~= 0 1680 | local canScrollDown: boolean = ScrollingFrame.CanvasPosition.Y 1681 | < ScrollingFrame.CanvasSize.Y.Offset - ScrollingFrame.AbsoluteWindowSize.Y 1682 | 1683 | ScrollUpInventoryButton.Visible = canScrollUp 1684 | ScrollDownInventoryButton.Visible = canScrollDown 1685 | end 1686 | end) 1687 | 1688 | -- Position the frames and sizes for the Backpack GUI elements 1689 | UpdateBackpackLayout() 1690 | 1691 | --Make the gamepad hint frame 1692 | local gamepadHintsFrame: Frame = Instance.new("Frame") 1693 | gamepadHintsFrame.Name = "GamepadHintsFrame" 1694 | gamepadHintsFrame.Size = UDim2.fromOffset(HotbarFrame.Size.X.Offset, (IsTenFootInterface and 95 or 60)) 1695 | gamepadHintsFrame.BackgroundTransparency = BACKGROUND_TRANSPARENCY 1696 | gamepadHintsFrame.BackgroundColor3 = BACKGROUND_COLOR 1697 | gamepadHintsFrame.Visible = false 1698 | gamepadHintsFrame.Parent = MainFrame 1699 | 1700 | local gamepadHintsFrameLayout: UIListLayout = Instance.new("UIListLayout") 1701 | gamepadHintsFrameLayout.Name = "Layout" 1702 | gamepadHintsFrameLayout.Padding = UDim.new(0, 25) 1703 | gamepadHintsFrameLayout.FillDirection = Enum.FillDirection.Horizontal 1704 | gamepadHintsFrameLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center 1705 | gamepadHintsFrameLayout.SortOrder = Enum.SortOrder.LayoutOrder 1706 | gamepadHintsFrameLayout.Parent = gamepadHintsFrame 1707 | 1708 | local gamepadHintsFrameCorner: UICorner = Instance.new("UICorner") 1709 | gamepadHintsFrameCorner.Name = "Corner" 1710 | gamepadHintsFrameCorner.CornerRadius = BACKGROUND_CORNER_RADIUS 1711 | gamepadHintsFrameCorner.Parent = gamepadHintsFrame 1712 | 1713 | local function addGamepadHint(hintImageString: string, hintTextString: string): () 1714 | local hintFrame: Frame = Instance.new("Frame") 1715 | hintFrame.Name = "HintFrame" 1716 | hintFrame.AutomaticSize = Enum.AutomaticSize.XY 1717 | hintFrame.BackgroundTransparency = 1 1718 | hintFrame.Parent = gamepadHintsFrame 1719 | 1720 | local hintLayout: UIListLayout = Instance.new("UIListLayout") 1721 | hintLayout.Name = "Layout" 1722 | hintLayout.Padding = (IsTenFootInterface and UDim.new(0, 20) or UDim.new(0, 12)) 1723 | hintLayout.FillDirection = Enum.FillDirection.Horizontal 1724 | hintLayout.SortOrder = Enum.SortOrder.LayoutOrder 1725 | hintLayout.VerticalAlignment = Enum.VerticalAlignment.Center 1726 | hintLayout.Parent = hintFrame 1727 | 1728 | local hintImage: ImageLabel = Instance.new("ImageLabel") 1729 | hintImage.Name = "HintImage" 1730 | hintImage.Size = (IsTenFootInterface and UDim2.fromOffset(60, 60) or UDim2.fromOffset(30, 30)) 1731 | hintImage.BackgroundTransparency = 1 1732 | hintImage.Image = hintImageString 1733 | hintImage.Parent = hintFrame 1734 | 1735 | local hintText: TextLabel = Instance.new("TextLabel") 1736 | hintText.Name = "HintText" 1737 | hintText.AutomaticSize = Enum.AutomaticSize.XY 1738 | hintText.FontFace = Font.new(FONT_FAMILY.Family, Enum.FontWeight.Medium, Enum.FontStyle.Normal) 1739 | hintText.TextSize = (IsTenFootInterface and 32 or 19) 1740 | hintText.BackgroundTransparency = 1 1741 | hintText.Text = hintTextString 1742 | hintText.TextColor3 = Color3.new(1, 1, 1) 1743 | hintText.TextXAlignment = Enum.TextXAlignment.Left 1744 | hintText.TextYAlignment = Enum.TextYAlignment.Center 1745 | hintText.TextWrapped = true 1746 | hintText.Parent = hintFrame 1747 | 1748 | local textSizeConstraint: UITextSizeConstraint = Instance.new("UITextSizeConstraint") 1749 | textSizeConstraint.MaxTextSize = hintText.TextSize 1750 | textSizeConstraint.Parent = hintText 1751 | end 1752 | 1753 | addGamepadHint(UserInputService:GetImageForKeyCode(Enum.KeyCode.ButtonX), "Remove From Hotbar") 1754 | addGamepadHint(UserInputService:GetImageForKeyCode(Enum.KeyCode.ButtonA), "Select/Swap") 1755 | addGamepadHint(UserInputService:GetImageForKeyCode(Enum.KeyCode.ButtonB), "Close Backpack") 1756 | 1757 | local function resizeGamepadHintsFrame(): () 1758 | gamepadHintsFrame.Size = 1759 | UDim2.new(HotbarFrame.Size.X.Scale, HotbarFrame.Size.X.Offset, 0, (IsTenFootInterface and 95 or 60)) 1760 | gamepadHintsFrame.Position = UDim2.new( 1761 | HotbarFrame.Position.X.Scale, 1762 | HotbarFrame.Position.X.Offset, 1763 | InventoryFrame.Position.Y.Scale, 1764 | InventoryFrame.Position.Y.Offset - gamepadHintsFrame.Size.Y.Offset - ICON_BUFFER_PIXELS 1765 | ) 1766 | 1767 | local spaceTaken: number = 0 1768 | 1769 | local gamepadHints: { Instance } = gamepadHintsFrame:GetChildren() 1770 | local filteredGamepadHints: any = {} 1771 | 1772 | for _, child: Instance in pairs(gamepadHints) do 1773 | if child:IsA("GuiObject") then 1774 | table.insert(filteredGamepadHints, child) 1775 | end 1776 | end 1777 | 1778 | --First get the total space taken by all the hints 1779 | for guiObjects = 1, #filteredGamepadHints do 1780 | if filteredGamepadHints[guiObjects]:IsA("GuiObject") then 1781 | filteredGamepadHints[guiObjects].Size = UDim2.new(1, 0, 1, -5) 1782 | filteredGamepadHints[guiObjects].Position = UDim2.new(0, 0, 0, 0) 1783 | spaceTaken = spaceTaken 1784 | + ( 1785 | filteredGamepadHints[guiObjects].HintText.Position.X.Offset 1786 | + filteredGamepadHints[guiObjects].HintText.TextBounds.X 1787 | ) 1788 | end 1789 | end 1790 | 1791 | --The space between all the frames should be equal 1792 | local spaceBetweenElements: number = (gamepadHintsFrame.AbsoluteSize.X - spaceTaken) / (#filteredGamepadHints - 1) 1793 | for i: number = 1, #filteredGamepadHints do 1794 | filteredGamepadHints[i].Position = ( 1795 | i == 1 and UDim2.new(0, 0, 0, 0) 1796 | or UDim2.new( 1797 | 0, 1798 | filteredGamepadHints[i - 1].Position.X.Offset 1799 | + filteredGamepadHints[i - 1].Size.X.Offset 1800 | + spaceBetweenElements, 1801 | 0, 1802 | 0 1803 | ) 1804 | ) 1805 | filteredGamepadHints[i].Size = UDim2.new( 1806 | 0, 1807 | (filteredGamepadHints[i].HintText.Position.X.Offset + filteredGamepadHints[i].HintText.TextBounds.X), 1808 | 1, 1809 | -5 1810 | ) 1811 | end 1812 | end 1813 | 1814 | local searchFrame: Frame = Instance.new("Frame") 1815 | do -- Search stuff 1816 | searchFrame.Name = "Search" 1817 | searchFrame.BackgroundColor3 = SEARCH_BACKGROUND_COLOR 1818 | searchFrame.BackgroundTransparency = SEARCH_BACKGROUND_TRANSPARENCY 1819 | searchFrame.Size = UDim2.new( 1820 | 0, 1821 | SEARCH_WIDTH_PIXELS - (SEARCH_BUFFER_PIXELS * 2), 1822 | 0, 1823 | INVENTORY_HEADER_SIZE - (SEARCH_BUFFER_PIXELS * 2) 1824 | ) 1825 | searchFrame.Position = UDim2.new(1, -searchFrame.Size.X.Offset - SEARCH_BUFFER_PIXELS, 0, SEARCH_BUFFER_PIXELS) 1826 | searchFrame.Parent = InventoryFrame 1827 | 1828 | local searchFrameCorner: UICorner = Instance.new("UICorner") 1829 | searchFrameCorner.Name = "Corner" 1830 | searchFrameCorner.CornerRadius = SEARCH_CORNER_RADIUS 1831 | searchFrameCorner.Parent = searchFrame 1832 | 1833 | local searchFrameBorder: UIStroke = Instance.new("UIStroke") 1834 | searchFrameBorder.Name = "Border" 1835 | searchFrameBorder.Color = SEARCH_BORDER_COLOR 1836 | searchFrameBorder.Thickness = SEARCH_BORDER_THICKNESS 1837 | searchFrameBorder.Transparency = SEARCH_BORDER_TRANSPARENCY 1838 | searchFrameBorder.Parent = searchFrame 1839 | 1840 | local searchBox: TextBox = Instance.new("TextBox") 1841 | searchBox.BackgroundTransparency = 1 1842 | searchBox.Name = "TextBox" 1843 | searchBox.Text = "" 1844 | searchBox.TextColor3 = TEXT_COLOR 1845 | searchBox.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY 1846 | searchBox.TextStrokeColor3 = TEXT_STROKE_COLOR 1847 | searchBox.FontFace = Font.new(FONT_FAMILY.Family, Enum.FontWeight.Medium, Enum.FontStyle.Normal) 1848 | searchBox.PlaceholderText = SEARCH_TEXT_PLACEHOLDER 1849 | searchBox.TextColor3 = TEXT_COLOR 1850 | searchBox.TextTransparency = TEXT_STROKE_TRANSPARENCY 1851 | searchBox.TextStrokeColor3 = TEXT_STROKE_COLOR 1852 | searchBox.ClearTextOnFocus = false 1853 | searchBox.TextTruncate = Enum.TextTruncate.AtEnd 1854 | searchBox.TextSize = FONT_SIZE 1855 | searchBox.TextXAlignment = Enum.TextXAlignment.Left 1856 | searchBox.TextYAlignment = Enum.TextYAlignment.Center 1857 | searchBox.Size = UDim2.new( 1858 | 0, 1859 | (SEARCH_WIDTH_PIXELS - (SEARCH_BUFFER_PIXELS * 2)) - (SEARCH_TEXT_OFFSET * 2) - 20, 1860 | 0, 1861 | INVENTORY_HEADER_SIZE - (SEARCH_BUFFER_PIXELS * 2) - (SEARCH_TEXT_OFFSET * 2) 1862 | ) 1863 | searchBox.AnchorPoint = Vector2.new(0, 0.5) 1864 | searchBox.Position = UDim2.new(0, SEARCH_TEXT_OFFSET, 0.5, 0) 1865 | searchBox.ZIndex = 2 1866 | searchBox.Parent = searchFrame 1867 | 1868 | local xButton: TextButton = Instance.new("TextButton") 1869 | xButton.Name = "X" 1870 | xButton.Text = "" 1871 | xButton.Size = UDim2.fromOffset(30, 30) 1872 | xButton.Position = UDim2.new(1, -xButton.Size.X.Offset, 0.5, -xButton.Size.Y.Offset / 2) 1873 | xButton.ZIndex = 4 1874 | xButton.Visible = false 1875 | xButton.BackgroundTransparency = 1 1876 | xButton.Parent = searchFrame 1877 | 1878 | local xImage: ImageButton = Instance.new("ImageButton") 1879 | xImage.Name = "X" 1880 | xImage.Image = SEARCH_IMAGE_X 1881 | xImage.BackgroundTransparency = 1 1882 | xImage.Size = UDim2.new( 1883 | 0, 1884 | searchFrame.Size.Y.Offset - (SEARCH_BUFFER_PIXELS * 4), 1885 | 0, 1886 | searchFrame.Size.Y.Offset - (SEARCH_BUFFER_PIXELS * 4) 1887 | ) 1888 | xImage.AnchorPoint = Vector2.new(0.5, 0.5) 1889 | xImage.Position = UDim2.fromScale(0.5, 0.5) 1890 | xImage.ZIndex = 1 1891 | xImage.BorderSizePixel = 0 1892 | xImage.Parent = xButton 1893 | 1894 | local function search(): () 1895 | local terms: { [string]: boolean } = {} 1896 | for word: string in searchBox.Text:gmatch("%S+") do 1897 | terms[word:lower()] = true 1898 | end 1899 | 1900 | local hitTable = {} 1901 | for i: number = NumberOfHotbarSlots + 1, #Slots do -- Only search inventory slots 1902 | local slot = Slots[i] 1903 | local hits: any = slot:CheckTerms(terms) 1904 | table.insert(hitTable, { slot, hits }) 1905 | slot.Frame.Visible = false 1906 | slot.Frame.Parent = InventoryFrame 1907 | end 1908 | 1909 | table.sort(hitTable, function(left: any, right: any): boolean 1910 | return left[2] > right[2] 1911 | end) 1912 | ViewingSearchResults = true 1913 | 1914 | local hitCount: number = 0 1915 | for _, data in ipairs(hitTable) do 1916 | local slot, hits: any = data[1], data[2] 1917 | if hits > 0 then 1918 | slot.Frame.Visible = true 1919 | slot.Frame.Parent = UIGridFrame 1920 | slot.Frame.LayoutOrder = NumberOfHotbarSlots + hitCount 1921 | hitCount = hitCount + 1 1922 | end 1923 | end 1924 | 1925 | ScrollingFrame.CanvasPosition = Vector2.new(0, 0) 1926 | UpdateScrollingFrameCanvasSize() 1927 | 1928 | xButton.ZIndex = 3 1929 | end 1930 | 1931 | local function clearResults(): () 1932 | if xButton.ZIndex > 0 then 1933 | ViewingSearchResults = false 1934 | for i: number = NumberOfHotbarSlots + 1, #Slots do 1935 | local slot = Slots[i] 1936 | slot.Frame.LayoutOrder = slot.Index 1937 | slot.Frame.Parent = UIGridFrame 1938 | slot.Frame.Visible = true 1939 | end 1940 | xButton.ZIndex = 0 1941 | end 1942 | UpdateScrollingFrameCanvasSize() 1943 | end 1944 | 1945 | local function reset(): () 1946 | clearResults() 1947 | searchBox.Text = "" 1948 | end 1949 | 1950 | local function onChanged(property: string): () 1951 | if property == "Text" then 1952 | local text: string = searchBox.Text 1953 | if text == "" then 1954 | searchBox.TextTransparency = TEXT_STROKE_TRANSPARENCY 1955 | clearResults() 1956 | elseif text ~= SEARCH_TEXT then 1957 | searchBox.TextTransparency = 0 1958 | search() 1959 | end 1960 | xButton.Visible = text ~= "" and text ~= SEARCH_TEXT 1961 | end 1962 | end 1963 | 1964 | local function focusLost(enterPressed: boolean): () 1965 | if enterPressed then 1966 | --TODO: Could optimize 1967 | search() 1968 | end 1969 | end 1970 | 1971 | xButton.MouseButton1Click:Connect(reset) 1972 | searchBox.Changed:Connect(onChanged) 1973 | searchBox.FocusLost:Connect(focusLost) 1974 | 1975 | BackpackScript.StateChanged.Event:Connect(function(isNowOpen: boolean): () 1976 | -- InventoryIcon:getInstance("iconButton").Modal = isNowOpen -- Allows free mouse movement even in first person 1977 | 1978 | if not isNowOpen then 1979 | reset() 1980 | end 1981 | end) 1982 | 1983 | HotkeyFns[Enum.KeyCode.Escape.Value] = function(isProcessed: any): () 1984 | if isProcessed then -- Pressed from within a TextBox 1985 | reset() 1986 | end 1987 | end 1988 | local function detectGamepad(lastInputType: Enum.UserInputType): () 1989 | if lastInputType == Enum.UserInputType.Gamepad1 and not UserInputService.VREnabled then 1990 | searchFrame.Visible = false 1991 | else 1992 | searchFrame.Visible = true 1993 | end 1994 | end 1995 | UserInputService.LastInputTypeChanged:Connect(detectGamepad) 1996 | end 1997 | 1998 | -- When menu is opend, disable backpack 1999 | GuiService.MenuOpened:Connect(function(): () 2000 | BackpackGui.Enabled = false 2001 | inventoryIcon:setEnabled(false) 2002 | end) 2003 | 2004 | -- When menu is closed, enable backpack 2005 | GuiService.MenuClosed:Connect(function(): () 2006 | BackpackGui.Enabled = true 2007 | inventoryIcon:setEnabled(true) 2008 | end) 2009 | 2010 | do -- Make the Inventory expand/collapse arrow (unless TopBar) 2011 | -- selene: allow(unused_variable) 2012 | local removeHotBarSlot = function(name: string, state: Enum.UserInputState, input: InputObject): () 2013 | if state ~= Enum.UserInputState.Begin then 2014 | return 2015 | end 2016 | if not GuiService.SelectedObject then 2017 | return 2018 | end 2019 | 2020 | for i: number = 1, NumberOfHotbarSlots do 2021 | if Slots[i].Frame == GuiService.SelectedObject and Slots[i].Tool then 2022 | Slots[i]:MoveToInventory() 2023 | return 2024 | end 2025 | end 2026 | end 2027 | 2028 | local function openClose(): () 2029 | if not next(Dragging) then -- Only continue if nothing is being dragged 2030 | InventoryFrame.Visible = not InventoryFrame.Visible 2031 | local nowOpen: boolean = InventoryFrame.Visible 2032 | AdjustHotbarFrames() 2033 | HotbarFrame.Active = not HotbarFrame.Active 2034 | for i: number = 1, NumberOfHotbarSlots do 2035 | Slots[i]:SetClickability(not nowOpen) 2036 | end 2037 | end 2038 | 2039 | if InventoryFrame.Visible then 2040 | if GamepadEnabled then 2041 | if GAMEPAD_INPUT_TYPES[UserInputService:GetLastInputType()] then 2042 | resizeGamepadHintsFrame() 2043 | gamepadHintsFrame.Visible = not UserInputService.VREnabled 2044 | end 2045 | enableGamepadInventoryControl() 2046 | end 2047 | if BackpackPanel and VRService.VREnabled then 2048 | BackpackPanel:SetVisible(true) 2049 | BackpackPanel:RequestPositionUpdate() 2050 | end 2051 | else 2052 | if GamepadEnabled then 2053 | gamepadHintsFrame.Visible = false 2054 | end 2055 | disableGamepadInventoryControl() 2056 | end 2057 | 2058 | if InventoryFrame.Visible then 2059 | ContextActionService:BindAction("BackpackRemoveSlot", removeHotBarSlot, false, Enum.KeyCode.ButtonX) 2060 | else 2061 | ContextActionService:UnbindAction("BackpackRemoveSlot") 2062 | end 2063 | 2064 | BackpackScript.IsOpen = InventoryFrame.Visible 2065 | BackpackScript.StateChanged:Fire(InventoryFrame.Visible) 2066 | end 2067 | 2068 | StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, false) 2069 | BackpackScript.OpenClose = openClose -- Exposed 2070 | end 2071 | 2072 | -- Now that we're done building the GUI, we Connect to all the major events 2073 | 2074 | -- Wait for the player if LocalPlayer wasn't ready earlier 2075 | while not Player do 2076 | task.wait() 2077 | Player = Players.LocalPlayer 2078 | end 2079 | 2080 | -- Listen to current and all future characters of our player 2081 | Player.CharacterAdded:Connect(OnCharacterAdded) 2082 | if Player.Character then 2083 | OnCharacterAdded(Player.Character) 2084 | end 2085 | 2086 | do -- Hotkey stuff 2087 | -- Listen to key down 2088 | UserInputService.InputBegan:Connect(OnInputBegan) 2089 | 2090 | -- Listen to ANY TextBox gaining or losing focus, for disabling all hotkeys 2091 | UserInputService.TextBoxFocused:Connect(function(): () 2092 | TextBoxFocused = true 2093 | end) 2094 | UserInputService.TextBoxFocusReleased:Connect(function(): () 2095 | TextBoxFocused = false 2096 | end) 2097 | 2098 | -- Manual unequip for HopperBins on drop button pressed 2099 | HotkeyFns[DROP_HOTKEY_VALUE] = function(): () --NOTE: HopperBin 2100 | if ActiveHopper then 2101 | UnequipAllTools() 2102 | end 2103 | end 2104 | 2105 | -- Listen to keyboard status, for showing/hiding hotkey labels 2106 | UserInputService.LastInputTypeChanged:Connect(OnUISChanged) 2107 | OnUISChanged() 2108 | 2109 | -- Listen to gamepad status, for allowing gamepad style selection/equip 2110 | if UserInputService:GetGamepadConnected(Enum.UserInputType.Gamepad1) then 2111 | gamepadConnected() 2112 | end 2113 | UserInputService.GamepadConnected:Connect(function(gamepadEnum: Enum.UserInputType): () 2114 | if gamepadEnum == Enum.UserInputType.Gamepad1 then 2115 | gamepadConnected() 2116 | end 2117 | end) 2118 | UserInputService.GamepadDisconnected:Connect(function(gamepadEnum: Enum.UserInputType): () 2119 | if gamepadEnum == Enum.UserInputType.Gamepad1 then 2120 | gamepadDisconnected() 2121 | end 2122 | end) 2123 | end 2124 | 2125 | -- Sets whether the backpack is enabled or not 2126 | function BackpackScript:SetBackpackEnabled(Enabled: boolean): () 2127 | BackpackEnabled = Enabled 2128 | end 2129 | 2130 | -- Returns if the backpack's inventory is open 2131 | function BackpackScript:IsOpened(): boolean 2132 | return BackpackScript.IsOpen 2133 | end 2134 | 2135 | -- Returns on if the backpack is enabled or not 2136 | function BackpackScript:GetBackpackEnabled(): boolean 2137 | return BackpackEnabled 2138 | end 2139 | 2140 | -- Returns the BindableEvent for when the backpack state changes 2141 | function BackpackScript:GetStateChangedEvent(): BindableEvent 2142 | return BackpackScript.StateChanged 2143 | end 2144 | 2145 | -- Update every heartbeat the icon state 2146 | RunService.Heartbeat:Connect(function(): () 2147 | OnIconChanged(BackpackEnabled) 2148 | end) 2149 | 2150 | -- Update the transparency of the backpack based on GuiService.PreferredTransparency 2151 | local function OnPreferredTransparencyChanged(): () 2152 | local preferredTransparency: number = GuiService.PreferredTransparency 2153 | 2154 | BACKGROUND_TRANSPARENCY = BACKGROUND_TRANSPARENCY_DEFAULT * preferredTransparency 2155 | InventoryFrame.BackgroundTransparency = BACKGROUND_TRANSPARENCY 2156 | 2157 | SLOT_LOCKED_TRANSPARENCY = SLOT_LOCKED_TRANSPARENCY_DEFAULT * preferredTransparency 2158 | for _, slot in ipairs(Slots) do 2159 | slot.Frame.BackgroundTransparency = SLOT_LOCKED_TRANSPARENCY 2160 | end 2161 | 2162 | SEARCH_BACKGROUND_TRANSPARENCY = SEARCH_BACKGROUND_TRANSPARENCY_DEFAULT * preferredTransparency 2163 | searchFrame.BackgroundTransparency = SEARCH_BACKGROUND_TRANSPARENCY 2164 | end 2165 | GuiService:GetPropertyChangedSignal("PreferredTransparency"):Connect(OnPreferredTransparencyChanged) 2166 | 2167 | return BackpackScript 2168 | --------------------------------------------------------------------------------