From 916ad13109f1054b3d7e19753ed5a46d16f4acf9 Mon Sep 17 00:00:00 2001 From: jnngl Date: Tue, 11 Jul 2023 06:08:57 +1100 Subject: [PATCH] First commit --- .github/workflows/build.yml | 56 ++ .gitignore | 118 ++++ HEADER.txt | 14 + LICENSE | 661 ++++++++++++++++++ README.md | 22 + VERSION | 1 + build.gradle | 117 ++++ config/checkstyle/checkstyle.xml | 365 ++++++++++ config/checkstyle/suppressions.xml | 16 + config/spotbugs/suppressions.xml | 10 + gradle.properties | 6 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 244 +++++++ gradlew.bat | 92 +++ settings.gradle | 1 + .../java/net/elytrium/limbohub/LimboHub.java | 656 +++++++++++++++++ .../java/net/elytrium/limbohub/Settings.java | 373 ++++++++++ .../elytrium/limbohub/command/HubCommand.java | 46 ++ .../limbohub/command/ReloadCommand.java | 42 ++ .../elytrium/limbohub/data/LinkedBossBar.java | 49 ++ .../net/elytrium/limbohub/data/Sidebar.java | 53 ++ .../elytrium/limbohub/entities/Hologram.java | 80 +++ .../net/elytrium/limbohub/entities/NPC.java | 180 +++++ .../limbohub/handler/HubSessionHandler.java | 244 +++++++ .../limbohub/listener/HubListener.java | 39 ++ .../java/net/elytrium/limbohub/menu/Menu.java | 55 ++ .../protocol/container/Container.java | 84 +++ .../protocol/container/ContainerType.java | 48 ++ .../protocol/entities/ArmorStand.java | 62 ++ .../limbohub/protocol/entities/Player.java | 42 ++ .../limbohub/protocol/item/ItemStack.java | 110 +++ .../protocol/item/meta/AbstractItemMeta.java | 164 +++++ .../limbohub/protocol/item/meta/ItemMeta.java | 26 + .../protocol/item/meta/StaticItemMeta.java | 43 ++ .../protocol/metadata/EntityMetadata.java | 52 ++ .../metadata/EntityMetadataBooleanEntry.java | 50 ++ .../metadata/EntityMetadataByteEntry.java | 44 ++ .../metadata/EntityMetadataEntry.java | 28 + .../EntityMetadataOptionalComponentEntry.java | 56 ++ .../metadata/EntityMetadataStringEntry.java | 55 ++ .../protocol/packets/ClickContainer.java | 64 ++ .../protocol/packets/CloseContainer.java | 57 ++ .../protocol/packets/DisplayObjective.java | 63 ++ .../limbohub/protocol/packets/Interact.java | 93 +++ .../limbohub/protocol/packets/OpenScreen.java | 66 ++ .../protocol/packets/ScoreboardTeam.java | 127 ++++ .../protocol/packets/SetContainerContent.java | 88 +++ .../protocol/packets/SetContainerSlot.java | 79 +++ .../protocol/packets/SetEntityMetadata.java | 61 ++ .../protocol/packets/SetHeadRotation.java | 63 ++ .../protocol/packets/SpawnEntity.java | 102 +++ .../protocol/packets/SpawnPlayer.java | 113 +++ .../protocol/packets/UpdateObjectives.java | 94 +++ .../protocol/packets/UpdateScore.java | 85 +++ .../net/elytrium/limbohub/BuildConstants.java | 24 + 56 files changed, 5588 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 HEADER.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 VERSION create mode 100644 build.gradle create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml create mode 100644 config/spotbugs/suppressions.xml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/net/elytrium/limbohub/LimboHub.java create mode 100644 src/main/java/net/elytrium/limbohub/Settings.java create mode 100644 src/main/java/net/elytrium/limbohub/command/HubCommand.java create mode 100644 src/main/java/net/elytrium/limbohub/command/ReloadCommand.java create mode 100644 src/main/java/net/elytrium/limbohub/data/LinkedBossBar.java create mode 100644 src/main/java/net/elytrium/limbohub/data/Sidebar.java create mode 100644 src/main/java/net/elytrium/limbohub/entities/Hologram.java create mode 100644 src/main/java/net/elytrium/limbohub/entities/NPC.java create mode 100644 src/main/java/net/elytrium/limbohub/handler/HubSessionHandler.java create mode 100644 src/main/java/net/elytrium/limbohub/listener/HubListener.java create mode 100644 src/main/java/net/elytrium/limbohub/menu/Menu.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/container/Container.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/container/ContainerType.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/entities/ArmorStand.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/entities/Player.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/item/ItemStack.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/item/meta/AbstractItemMeta.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/item/meta/ItemMeta.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/item/meta/StaticItemMeta.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadata.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataBooleanEntry.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataByteEntry.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataEntry.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataOptionalComponentEntry.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataStringEntry.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/ClickContainer.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/CloseContainer.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/DisplayObjective.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/Interact.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/OpenScreen.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/ScoreboardTeam.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerContent.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerSlot.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/SetEntityMetadata.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/SetHeadRotation.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/SpawnEntity.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/SpawnPlayer.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/UpdateObjectives.java create mode 100644 src/main/java/net/elytrium/limbohub/protocol/packets/UpdateScore.java create mode 100644 src/main/templates/net/elytrium/limbohub/BuildConstants.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1b66794 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,56 @@ +name: Java CI with Gradle + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v3.0.0 + - name: Set up JDK + uses: actions/setup-java@v3.0.0 + with: + distribution: adopt + java-version: 11 + - name: Build LimboHub + run: ./gradlew build + - name: Upload LimboHub + uses: actions/upload-artifact@v3.0.0 + with: + name: LimboHub + path: "build/libs/limbohub*.jar" + - uses: dev-drprasad/delete-tag-and-release@v0.2.1 + if: ${{ github.event_name == 'push' }} + continue-on-error: true + with: + delete_release: true + tag_name: dev-build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Find git version + id: git-version + run: echo "id=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - name: Find correct JAR + if: ${{ github.event_name == 'push' }} + id: find-jar + run: | + output="$(find build/libs/ ! -name "*-javadoc.jar" ! -name "*-sources.jar" -type f -printf "%f\n")" + echo "::set-output name=jarname::$output" + - name: Release the build + if: ${{ github.event_name == 'push' }} + uses: ncipollo/release-action@v1 + with: + artifacts: build/libs/${{ steps.find-jar.outputs.jarname }} + body: ${{ join(github.event.commits.*.message, '\n') }} + prerelease: true + name: Dev-build ${{ steps.git-version.outputs.id }} + tag: dev-build \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c37caf --- /dev/null +++ b/.gitignore @@ -0,0 +1,118 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Cache of project +.gradletasknamecache + +**/build/ + +# Common working directory +run/ + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/HEADER.txt b/HEADER.txt new file mode 100644 index 0000000..bc624b0 --- /dev/null +++ b/HEADER.txt @@ -0,0 +1,14 @@ +Copyright (C) 2023 Elytrium + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29ebfa5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0999026 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# LimboHub + +[![Join our Discord](https://img.shields.io/discord/775778822334709780.svg?logo=discord&label=Discord)](https://ely.su/discord) + +Virtual hub on Velocity proxy, built with LimboAPI. + +## Features + +--- + + - Fully virtual hub on Velocity, without the need to create a separate server. + - Configurable custom commands, NPCs, menus, holograms, boss bars, sidebar etc... + - Supported client versions: 1.8 - 1.20.1. + - Structure/MCEdit world formats supported. + - and more... + +## Commands and permissions + +--- + +- ***limbohub.command.hub* (See `hub-command.require-permission`) | See `hub-command.aliases`** - Sends player to the hub. +- ***limbohub.command.reload* | /limbohubreload** - Plugin reload command. \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..afaf360 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..f6119de --- /dev/null +++ b/build.gradle @@ -0,0 +1,117 @@ +//file:noinspection GroovyAssignabilityCheck + +plugins { + id("java") + id("checkstyle") + id("com.github.spotbugs").version("5.0.6") + id("org.cadixdev.licenser").version("0.6.1") + id("com.github.johnrengelman.shadow").version("7.1.2") +} + +setGroup("net.elytrium") +setVersion("1.0.0") + +compileJava { + getOptions().setEncoding("UTF-8") +} + +java { + setSourceCompatibility(JavaVersion.VERSION_11) + setTargetCompatibility(JavaVersion.VERSION_11) +} + +repositories { + mavenCentral() + + maven { + setName("elytrium-repo") + setUrl("https://maven.elytrium.net/repo/") + } + maven { + setName("papermc-repo") + setUrl("https://papermc.io/repo/repository/maven-public/") + } +} + +dependencies { + compileOnly("net.elytrium.limboapi:api:$limboapiVersion") + compileOnly("net.elytrium.commons:config:$elytriumCommonsVersion") + compileOnly("net.elytrium.commons:utils:$elytriumCommonsVersion") + compileOnly("net.elytrium.commons:velocity:$elytriumCommonsVersion") + compileOnly("net.elytrium.commons:kyori:$elytriumCommonsVersion") + + compileOnly("com.velocitypowered:velocity-api:$velocityVersion") + annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion") + compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion") // From Elytrium Repo. + + compileOnly("io.netty:netty-codec:$nettyVersion") + + compileOnly("com.github.spotbugs:spotbugs-annotations:$spotbugsVersion") + + implementation("org.bstats:bstats-velocity:$bstatsVersion") +} + +shadowJar { + getArchiveClassifier().set("") + + relocate("org.bstats", "net.elytrium.limbohub.thirdparty.org.bstats") + relocate("net.elytrium.commons.velocity", "net.elytrium.limboapi.thirdparty.commons.velocity") + relocate("net.elytrium.commons.kyori", "net.elytrium.limboapi.thirdparty.commons.kyori") + relocate("net.elytrium.commons.config", "net.elytrium.limboapi.thirdparty.commons.config") +} + +license { + setHeader(file("HEADER.txt")) +} + +checkstyle { + setToolVersion("10.1") + setConfigFile(file("${this.getRootDir()}/config/checkstyle/checkstyle.xml")) + setConfigProperties("configDirectory": "${this.getRootDir()}/config/checkstyle") + + // The build should immediately fail if we have errors. + setMaxErrors(0) + setMaxWarnings(0) +} + +spotbugsMain { + setExcludeFilter(file("${this.getRootDir()}/config/spotbugs/suppressions.xml")) + + reports { + html { + getRequired().set(true) + getOutputLocation().set(file("${this.getBuildDir()}/reports/spotbugs/main/spotbugs.html")) + setStylesheet("fancy-hist.xsl") + } + } +} + +sourceSets.main.getJava().srcDir( + getTasks().register("generateTemplates", Copy) { + task -> { + String version = getVersion().contains("-") ? "${getVersion()} (git-${getCurrentShortRevision()})" : getVersion() + task.getInputs().properties("version": version) + task.from(file("src/main/templates")).into(getLayout().getBuildDirectory().dir("generated/sources/templates")) + task.expand("version": version) + } + }.map { + it.getOutputs() + } +) + +assemble.dependsOn(shadowJar) + +String getCurrentShortRevision() { + OutputStream outputStream = new ByteArrayOutputStream() + exec { + if (System.getProperty("os.name").toLowerCase().contains("win")) { + commandLine("cmd", "/c", "git rev-parse --short HEAD") + } else { + commandLine("bash", "-c", "git rev-parse --short HEAD") + } + + setStandardOutput(outputStream) + } + + return outputStream.toString().trim() +} \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..228d035 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000..1018984 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/spotbugs/suppressions.xml b/config/spotbugs/suppressions.xml new file mode 100644 index 0000000..5f496ac --- /dev/null +++ b/config/spotbugs/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..30bfa4a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +limboapiVersion=1.1.14 +velocityVersion=3.2.0-SNAPSHOT +nettyVersion=4.1.86.Final +spotbugsVersion=4.7.3 +elytriumCommonsVersion=1.2.5-1 +bstatsVersion=3.0.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..4605298 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..79a61d4 --- /dev/null +++ b/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..be9ab38 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +getRootProject().setName("limbohub") \ No newline at end of file diff --git a/src/main/java/net/elytrium/limbohub/LimboHub.java b/src/main/java/net/elytrium/limbohub/LimboHub.java new file mode 100644 index 0000000..2fa5260 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/LimboHub.java @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub; + +import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.event.EventManager; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.plugin.Dependency; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.elytrium.commons.kyori.serialization.Serializer; +import net.elytrium.commons.kyori.serialization.Serializers; +import net.elytrium.commons.utils.updates.UpdatesChecker; +import net.elytrium.limboapi.api.Limbo; +import net.elytrium.limboapi.api.LimboFactory; +import net.elytrium.limboapi.api.chunk.VirtualWorld; +import net.elytrium.limboapi.api.command.LimboCommandMeta; +import net.elytrium.limboapi.api.file.WorldFile; +import net.elytrium.limboapi.api.material.VirtualItem; +import net.elytrium.limboapi.api.protocol.PacketDirection; +import net.elytrium.limboapi.api.protocol.packets.PacketMapping; +import net.elytrium.limbohub.command.HubCommand; +import net.elytrium.limbohub.command.ReloadCommand; +import net.elytrium.limbohub.data.LinkedBossBar; +import net.elytrium.limbohub.data.Sidebar; +import net.elytrium.limbohub.entities.Hologram; +import net.elytrium.limbohub.entities.NPC; +import net.elytrium.limbohub.handler.HubSessionHandler; +import net.elytrium.limbohub.listener.HubListener; +import net.elytrium.limbohub.menu.Menu; +import net.elytrium.limbohub.protocol.container.Container; +import net.elytrium.limbohub.protocol.item.ItemStack; +import net.elytrium.limbohub.protocol.item.meta.StaticItemMeta; +import net.elytrium.limbohub.protocol.packets.ClickContainer; +import net.elytrium.limbohub.protocol.packets.CloseContainer; +import net.elytrium.limbohub.protocol.packets.DisplayObjective; +import net.elytrium.limbohub.protocol.packets.Interact; +import net.elytrium.limbohub.protocol.packets.OpenScreen; +import net.elytrium.limbohub.protocol.packets.ScoreboardTeam; +import net.elytrium.limbohub.protocol.packets.SetContainerContent; +import net.elytrium.limbohub.protocol.packets.SetContainerSlot; +import net.elytrium.limbohub.protocol.packets.SetEntityMetadata; +import net.elytrium.limbohub.protocol.packets.SetHeadRotation; +import net.elytrium.limbohub.protocol.packets.SpawnEntity; +import net.elytrium.limbohub.protocol.packets.SpawnPlayer; +import net.elytrium.limbohub.protocol.packets.UpdateObjectives; +import net.elytrium.limbohub.protocol.packets.UpdateScore; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.bstats.charts.SimplePie; +import org.bstats.velocity.Metrics; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.slf4j.Logger; + +@Plugin( + id = "limbohub", + name = "LimboHub", + version = BuildConstants.VERSION, + authors = { + "JNNGL" + }, + dependencies = { + @Dependency(id = "limboapi") + } +) +public class LimboHub { + + private static int ENTITY_ID_COUNTER; + + @MonotonicNonNull + private static Logger LOGGER; + @MonotonicNonNull + private static Serializer SERIALIZER; + + private final Map menus = new HashMap<>(); + private final Map commands = new HashMap<>(); + private final Map npcs = new HashMap<>(); + private final List holograms = new ArrayList<>(); + private final Path dataDirectory; + private final File configFile; + private final ProxyServer server; + private final Metrics.Factory metricsFactory; + private final LimboFactory limboFactory; + + private String currentHubCommand = null; + private LinkedBossBar bossBar; + private Menu defaultMenu; + private Sidebar sidebar; + private Limbo hubServer; + + @Inject + public LimboHub(Logger logger, ProxyServer server, Metrics.Factory metricsFactory, @DataDirectory Path dataDirectory) { + setLogger(logger); + + this.server = server; + this.metricsFactory = metricsFactory; + this.dataDirectory = dataDirectory; + this.configFile = this.dataDirectory.resolve("config.yml").toFile(); + + this.limboFactory = (LimboFactory) this.server.getPluginManager().getPlugin("limboapi").flatMap(PluginContainer::getInstance).orElseThrow(); + } + + private String roundStat(int stat) { + if (stat == 0) { + return "0"; + } + + if (stat >= 25) { + return "25+"; + } + + int rounded = (stat / 5) * 5; + return Math.max(rounded, 1) + "-" + (rounded + 4); + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + Settings.IMP.setLogger(LOGGER); + + this.reload(); + + Metrics metrics = this.metricsFactory.make(this, 19042); + metrics.addCustomChart(new SimplePie("sidebar_enabled", () -> String.valueOf(Settings.IMP.MAIN.SIDEBAR.ENABLED))); + metrics.addCustomChart(new SimplePie("boss_bars", () -> this.roundStat(Settings.IMP.MAIN.BOSSBARS.size()))); + metrics.addCustomChart(new SimplePie("commands", () -> this.roundStat(Settings.IMP.MAIN.COMMANDS.size()))); + metrics.addCustomChart(new SimplePie("menus", () -> this.roundStat(Settings.IMP.MAIN.MENUS.size()))); + metrics.addCustomChart(new SimplePie("npcs", () -> this.roundStat(Settings.IMP.MAIN.NPCS.size()))); + metrics.addCustomChart(new SimplePie("holograms", () -> this.roundStat(Settings.IMP.MAIN.HOLOGRAMS.size()))); + + try { + if (!UpdatesChecker.checkVersionByURL("https://raw.githubusercontent.com/Elytrium/LimboHub/master/VERSION", Settings.IMP.VERSION)) { + LOGGER.error("****************************************"); + LOGGER.warn("The new LimboHub update was found, please update."); + LOGGER.error("https://github.com/Elytrium/LimboHub/releases/"); + LOGGER.error("****************************************"); + } + } catch (Exception e) { + LOGGER.error("Unable to check for updates."); + } + } + + public void reload() { + Settings.IMP.reload(this.configFile); + + resetEntityCounter(); + + ComponentSerializer serializer = Settings.IMP.SERIALIZER.getSerializer(); + if (serializer == null) { + LOGGER.warn("The specified serializer could not be found, using default. (LEGACY_AMPERSAND)"); + setSerializer(new Serializer(Objects.requireNonNull(Serializers.LEGACY_AMPERSAND.getSerializer()))); + } else { + setSerializer(new Serializer(serializer)); + } + + Settings.MAIN.PLAYER_COORDS playerCoords = Settings.IMP.MAIN.PLAYER_COORDS; + VirtualWorld hubWorld = this.limboFactory.createVirtualWorld( + Settings.IMP.MAIN.DIMENSION, + playerCoords.X, playerCoords.Y, playerCoords.Z, + (float) playerCoords.YAW, (float) playerCoords.PITCH + ); + + try { + Path worldPath = this.dataDirectory.resolve(Settings.IMP.MAIN.WORLD_FILE_PATH); + if (Files.exists(worldPath)) { + WorldFile worldFile = this.limboFactory.openWorldFile(Settings.IMP.MAIN.WORLD_FILE_TYPE, worldPath); + + Settings.MAIN.WORLD_COORDS worldCoords = Settings.IMP.MAIN.WORLD_COORDS; + worldFile.toWorld(this.limboFactory, hubWorld, worldCoords.X, worldCoords.Y, worldCoords.Z, Settings.IMP.MAIN.WORLD_LIGHT_LEVEL); + } else { + LOGGER.warn("World '{}' could not be found.", worldPath.getFileName()); + } + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + + if (Settings.IMP.MAIN.WORLD_OVERRIDE_BLOCK_LIGHT_LEVEL) { + hubWorld.fillBlockLight(Settings.IMP.MAIN.WORLD_LIGHT_LEVEL); + } + + this.menus.clear(); + Settings.IMP.MAIN.MENUS.forEach(menu -> { + if (menu.MENU_ID.isBlank()) { + throw new IllegalArgumentException("Menu ID cannot be blank."); + } + + Map items = menu.ITEMS.stream() + .collect(Collectors.toUnmodifiableMap( + item -> { + if (item.ID.length() != 1) { + throw new IllegalArgumentException("Item ID should be 1 character length."); + } + + return item.ID; + }, + item -> { + VirtualItem virtualItem = this.limboFactory.getItem(item.ITEM); + if (virtualItem == null) { + throw new IllegalArgumentException("Item '" + item.ITEM + "' not found."); + } + + List fallbackItems = item.FALLBACK_ITEMS.stream().map(id -> { + VirtualItem fallbackVirtualItem = this.limboFactory.getItem(id); + if (fallbackVirtualItem == null) { + throw new IllegalArgumentException("Fallback item '" + id + "' for '" + item.ITEM + "' not found."); + } + + return fallbackVirtualItem; + }).collect(Collectors.toUnmodifiableList()); + + Component nameComponent = buildResetComponent().append(getSerializer().deserialize(item.CUSTOM_NAME)); + List loreComponents = item.LORE == null ? null : + item.LORE.stream() + .map(line -> buildResetComponent().append(getSerializer().deserialize(line))) + .collect(Collectors.toList()); + + return new ItemStack(virtualItem, fallbackItems, item.COUNT, 0, new StaticItemMeta( + nameComponent, loreComponents, item.HAS_COLOR, item.COLOR, item.ENCHANTED, item.SKULL_OWNER)); + } + )); + + if (menu.MENU_CONTENTS.isEmpty()) { + throw new IllegalArgumentException("Menu (" + menu.MENU_ID + ") cannot be empty."); + } + + int columns = menu.MENU_CONTENTS.get(0).length(); + int rows = menu.MENU_CONTENTS.size(); + + Component titleComponent = getSerializer().deserialize(menu.TITLE); + Container container = Container.genericContainer(columns, rows, titleComponent); + + for (int row = 0; row < rows; row++) { + String format = menu.MENU_CONTENTS.get(row); + if (format.length() != columns) { + throw new IllegalArgumentException("Row #" + row + " length doesn't match first row length in menu " + menu.MENU_ID); + } + + for (int column = 0; column < columns; column++) { + String itemId = format.substring(column, column + 1); + ItemStack item = " ".equals(itemId) ? ItemStack.EMPTY : items.get(itemId); + container.setItem(column, row, item); + } + } + + Map actions = new HashMap<>(); + menu.ACTIONS.forEach(action -> action.SLOTS.forEach(slot -> { + int slotIndex; + if (slot.contains(",")) { + int[] position = Arrays.stream(slot.split(",")).mapToInt(Integer::parseInt).toArray(); + slotIndex = position[1] * columns + position[0]; + } else { + slotIndex = Integer.parseInt(slot); + } + + actions.put(slotIndex, action.ACTION); + })); + + this.menus.put(menu.MENU_ID, new Menu(container, actions, menu.DEFAULT_ACTION.getAction())); + }); + + if (!Settings.IMP.MAIN.BOSSBARS.isEmpty()) { + this.bossBar = this.buildLinkedBossBar(0, null); + } else { + this.bossBar = null; + } + + if (Settings.IMP.MAIN.DEFAULT_MENU != null && !Settings.IMP.MAIN.DEFAULT_MENU.isBlank()) { + this.defaultMenu = this.menus.get(Settings.IMP.MAIN.DEFAULT_MENU); + } else { + this.defaultMenu = null; + } + + if (Settings.IMP.MAIN.SIDEBAR.ENABLED) { + this.sidebar = new Sidebar( + getSerializer().deserialize(Settings.IMP.MAIN.SIDEBAR.TITLE), + Settings.IMP.MAIN.SIDEBAR.LINES.stream().map(getSerializer()::deserialize).collect(Collectors.toUnmodifiableList()) + ); + } else { + this.sidebar = null; + } + + this.npcs.clear(); + Settings.IMP.MAIN.NPCS.forEach(data -> { + NPC npc = new NPC( + data.DISPLAY_NAME.isBlank() ? null : getSerializer().deserialize(data.DISPLAY_NAME), data.X, data.Y, + data.Z, (float) data.YAW, (float) data.PITCH, data.LOAD_SKIN ? data.SKIN_DATA : null, data.ACTION + ); + + this.npcs.put(npc.getEntityId(), npc); + }); + + this.holograms.clear(); + Settings.IMP.MAIN.HOLOGRAMS.forEach(hologram -> this.holograms.add( + new Hologram( + hologram.X, hologram.Y, hologram.Z, + hologram.LINES.stream().map(getSerializer()::deserialize).collect(Collectors.toUnmodifiableList()) + ) + )); + + if (this.hubServer != null) { + this.hubServer.dispose(); + } + + this.hubServer = this.limboFactory + .createLimbo(hubWorld) + .setName("LimboHub") + .setWorldTime(Settings.IMP.MAIN.WORLD_TICKS) + .setGameMode(Settings.IMP.MAIN.GAME_MODE) + .registerPacket(PacketDirection.CLIENTBOUND, SpawnEntity.class, SpawnEntity::new, new PacketMapping[]{ + new PacketMapping(0x0E, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x00, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x01, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, SpawnPlayer.class, SpawnPlayer::new, new PacketMapping[]{ + new PacketMapping(0x0C, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x05, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x04, ProtocolVersion.MINECRAFT_1_16, true), + new PacketMapping(0x02, ProtocolVersion.MINECRAFT_1_19, true), + new PacketMapping(0x03, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, CloseContainer.class, CloseContainer::new, new PacketMapping[]{ + new PacketMapping(0x2E, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x12, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x13, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x13, ProtocolVersion.MINECRAFT_1_16, true), + new PacketMapping(0x12, ProtocolVersion.MINECRAFT_1_16_2, true), + new PacketMapping(0x13, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x10, ProtocolVersion.MINECRAFT_1_19, true), + new PacketMapping(0x0F, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x11, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, SetContainerContent.class, SetContainerContent::new, new PacketMapping[]{ + new PacketMapping(0x30, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x15, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x15, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_16, true), + new PacketMapping(0x13, ProtocolVersion.MINECRAFT_1_16_2, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x11, ProtocolVersion.MINECRAFT_1_19, true), + new PacketMapping(0x10, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x12, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, SetContainerSlot.class, SetContainerSlot::new, new PacketMapping[]{ + new PacketMapping(0x2F, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x16, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x17, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x16, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x17, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x16, ProtocolVersion.MINECRAFT_1_16, true), + new PacketMapping(0x15, ProtocolVersion.MINECRAFT_1_16_2, true), + new PacketMapping(0x16, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x13, ProtocolVersion.MINECRAFT_1_19, true), + new PacketMapping(0x12, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, OpenScreen.class, OpenScreen::new, new PacketMapping[]{ + new PacketMapping(0x2D, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x13, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x14, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x2E, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x2F, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x2E, ProtocolVersion.MINECRAFT_1_16, true), + new PacketMapping(0x2D, ProtocolVersion.MINECRAFT_1_16_2, true), + new PacketMapping(0x2E, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x2B, ProtocolVersion.MINECRAFT_1_19, true), + new PacketMapping(0x2D, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x2C, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x30, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, SetHeadRotation.class, SetHeadRotation::new, new PacketMapping[]{ + new PacketMapping(0x19, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x34, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x35, ProtocolVersion.MINECRAFT_1_12, true), + new PacketMapping(0x36, ProtocolVersion.MINECRAFT_1_12_1, true), + new PacketMapping(0x39, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x3B, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x3C, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x3B, ProtocolVersion.MINECRAFT_1_16, true), + new PacketMapping(0x3A, ProtocolVersion.MINECRAFT_1_16_2, true), + new PacketMapping(0x3E, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x3C, ProtocolVersion.MINECRAFT_1_19, true), + new PacketMapping(0x3F, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x3E, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x42, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, DisplayObjective.class, DisplayObjective::new, new PacketMapping[]{ + new PacketMapping(0x3D, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x38, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x3A, ProtocolVersion.MINECRAFT_1_12, true), + new PacketMapping(0x3B, ProtocolVersion.MINECRAFT_1_12_1, true), + new PacketMapping(0x3E, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x42, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x43, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x4C, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x4F, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x4D, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x51, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, SetEntityMetadata.class, SetEntityMetadata::new, new PacketMapping[]{ + new PacketMapping(0x1C, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x39, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x3B, ProtocolVersion.MINECRAFT_1_12, true), + new PacketMapping(0x3C, ProtocolVersion.MINECRAFT_1_12_1, true), + new PacketMapping(0x3F, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x43, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x44, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x4D, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x50, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x4E, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x52, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, UpdateObjectives.class, UpdateObjectives::new, new PacketMapping[]{ + new PacketMapping(0x3B, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x3F, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x41, ProtocolVersion.MINECRAFT_1_12, true), + new PacketMapping(0x42, ProtocolVersion.MINECRAFT_1_12_1, true), + new PacketMapping(0x45, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x49, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x4A, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x53, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x56, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x54, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x58, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, ScoreboardTeam.class, ScoreboardTeam::new, new PacketMapping[]{ + new PacketMapping(0x3E, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x41, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x43, ProtocolVersion.MINECRAFT_1_12, true), + new PacketMapping(0x44, ProtocolVersion.MINECRAFT_1_12_1, true), + new PacketMapping(0x47, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x4B, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x4C, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x55, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x58, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x56, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x5A, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.CLIENTBOUND, UpdateScore.class, UpdateScore::new, new PacketMapping[]{ + new PacketMapping(0x3C, ProtocolVersion.MINECRAFT_1_8, true), + new PacketMapping(0x42, ProtocolVersion.MINECRAFT_1_9, true), + new PacketMapping(0x44, ProtocolVersion.MINECRAFT_1_12, true), + new PacketMapping(0x45, ProtocolVersion.MINECRAFT_1_12_1, true), + new PacketMapping(0x48, ProtocolVersion.MINECRAFT_1_13, true), + new PacketMapping(0x4C, ProtocolVersion.MINECRAFT_1_14, true), + new PacketMapping(0x4D, ProtocolVersion.MINECRAFT_1_15, true), + new PacketMapping(0x56, ProtocolVersion.MINECRAFT_1_17, true), + new PacketMapping(0x59, ProtocolVersion.MINECRAFT_1_19_1, true), + new PacketMapping(0x57, ProtocolVersion.MINECRAFT_1_19_3, true), + new PacketMapping(0x5B, ProtocolVersion.MINECRAFT_1_19_4, true), + }) + .registerPacket(PacketDirection.SERVERBOUND, Interact.class, Interact::new, new PacketMapping[]{ + new PacketMapping(0x02, ProtocolVersion.MINECRAFT_1_8, false), + new PacketMapping(0x0A, ProtocolVersion.MINECRAFT_1_9, false), + new PacketMapping(0x0B, ProtocolVersion.MINECRAFT_1_12, false), + new PacketMapping(0x0A, ProtocolVersion.MINECRAFT_1_12_1, false), + new PacketMapping(0x0D, ProtocolVersion.MINECRAFT_1_13, false), + new PacketMapping(0x0E, ProtocolVersion.MINECRAFT_1_14, false), + new PacketMapping(0x0D, ProtocolVersion.MINECRAFT_1_17, false), + new PacketMapping(0x0F, ProtocolVersion.MINECRAFT_1_19, false), + new PacketMapping(0x10, ProtocolVersion.MINECRAFT_1_19_1, false), + new PacketMapping(0x0F, ProtocolVersion.MINECRAFT_1_19_3, false), + new PacketMapping(0x10, ProtocolVersion.MINECRAFT_1_19_4, false), + }) + .registerPacket(PacketDirection.SERVERBOUND, ClickContainer.class, ClickContainer::new, new PacketMapping[]{ + new PacketMapping(0x0E, ProtocolVersion.MINECRAFT_1_8, false), + new PacketMapping(0x07, ProtocolVersion.MINECRAFT_1_9, false), + new PacketMapping(0x08, ProtocolVersion.MINECRAFT_1_12, false), + new PacketMapping(0x07, ProtocolVersion.MINECRAFT_1_12_1, false), + new PacketMapping(0x08, ProtocolVersion.MINECRAFT_1_13, false), + new PacketMapping(0x09, ProtocolVersion.MINECRAFT_1_14, false), + new PacketMapping(0x08, ProtocolVersion.MINECRAFT_1_17, false), + new PacketMapping(0x0A, ProtocolVersion.MINECRAFT_1_19, false), + new PacketMapping(0x0B, ProtocolVersion.MINECRAFT_1_19_1, false), + new PacketMapping(0x0A, ProtocolVersion.MINECRAFT_1_19_3, false), + new PacketMapping(0x0B, ProtocolVersion.MINECRAFT_1_19_4, false), + }); + + this.commands.clear(); + Settings.IMP.MAIN.COMMANDS.forEach(command -> { + List aliases = Stream.concat(Stream.of(command.COMMAND), command.ALIASES.stream()).collect(Collectors.toUnmodifiableList()); + aliases.forEach(alias -> this.commands.put(alias, command.ACTION)); + this.hubServer.registerCommand(new LimboCommandMeta(aliases)); + }); + + EventManager eventManager = this.server.getEventManager(); + eventManager.unregisterListeners(this); + eventManager.register(this, new HubListener(this)); + + CommandManager commandManager = this.server.getCommandManager(); + if (this.currentHubCommand != null) { + commandManager.unregister(this.currentHubCommand); + } + + if (!Settings.IMP.MAIN.HUB_COMMAND.ALIASES.isEmpty()) { + String mainAlias = Settings.IMP.MAIN.HUB_COMMAND.ALIASES.get(0); + String[] otherAliases = Settings.IMP.MAIN.HUB_COMMAND.ALIASES.stream().skip(1).toArray(String[]::new); + commandManager.register(mainAlias, new HubCommand(this), otherAliases); + this.currentHubCommand = mainAlias; + } else { + this.currentHubCommand = null; + } + + commandManager.unregister("limbohubreload"); + commandManager.register("limbohubreload", new ReloadCommand(this)); + } + + public LinkedBossBar buildLinkedBossBar(int index, LinkedBossBar first) { + Settings.MAIN.BOSSBAR data = Settings.IMP.MAIN.BOSSBARS.get(index); + LinkedBossBar linkedBossBar; + + BossBar currentBossBar; + if (!data.HIDDEN) { + currentBossBar = BossBar.bossBar(getSerializer().deserialize(data.NAME), (float) data.PROGRESS, data.COLOR, data.OVERLAY); + } else { + currentBossBar = null; + } + + if (data.STAY_TIME_MILLIS == -1) { + linkedBossBar = new LinkedBossBar(currentBossBar, -1, null); + } else { + linkedBossBar = new LinkedBossBar(currentBossBar, data.STAY_TIME_MILLIS, null); + if (index + 1 >= Settings.IMP.MAIN.BOSSBARS.size()) { + linkedBossBar.setNext(first); + } else { + linkedBossBar.setNext(this.buildLinkedBossBar(index + 1, first != null ? first : linkedBossBar)); + } + } + + return linkedBossBar; + } + + public void sendToHub(Player player) { + this.hubServer.spawnPlayer(player, new HubSessionHandler(player, this)); + } + + public Path getDataDirectory() { + return this.dataDirectory; + } + + public File getConfigFile() { + return this.configFile; + } + + public ProxyServer getServer() { + return this.server; + } + + public LimboFactory getLimboFactory() { + return this.limboFactory; + } + + public Limbo getHubServer() { + return this.hubServer; + } + + public Map getMenus() { + return this.menus; + } + + public Map getCommands() { + return this.commands; + } + + public Map getNpcs() { + return this.npcs; + } + + public List getHolograms() { + return this.holograms; + } + + public LinkedBossBar getBossBar() { + return this.bossBar; + } + + public Sidebar getSidebar() { + return this.sidebar; + } + + public Menu getDefaultMenu() { + return this.defaultMenu; + } + + private static void setLogger(Logger logger) { + LOGGER = logger; + } + + public static Logger getLogger() { + return LOGGER; + } + + private static void setSerializer(Serializer serializer) { + SERIALIZER = serializer; + } + + public static Serializer getSerializer() { + return SERIALIZER; + } + + public static Component buildResetComponent() { + return Component.empty().decoration(TextDecoration.ITALIC, false); + } + + private static void resetEntityCounter() { + ENTITY_ID_COUNTER = 10; + } + + public static int reserveEntityId() { + return ENTITY_ID_COUNTER++; + } + + public static int reserveEntityIds(int count) { + try { + return ENTITY_ID_COUNTER; + } finally { + ENTITY_ID_COUNTER += count; + } + } +} diff --git a/src/main/java/net/elytrium/limbohub/Settings.java b/src/main/java/net/elytrium/limbohub/Settings.java new file mode 100644 index 0000000..8d10c7b --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/Settings.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub; + +import java.util.List; +import net.elytrium.commons.config.YamlConfig; +import net.elytrium.commons.kyori.serialization.Serializers; +import net.elytrium.limboapi.api.chunk.Dimension; +import net.elytrium.limboapi.api.file.BuiltInWorldFileType; +import net.elytrium.limboapi.api.player.GameMode; +import net.kyori.adventure.bossbar.BossBar; + +public class Settings extends YamlConfig { + + @Ignore + public static final Settings IMP = new Settings(); + + public String VERSION = BuildConstants.VERSION; + + @Comment({ + "Available serializers:", + "LEGACY_AMPERSAND - \"&c&lExample &c&9Text\".", + "LEGACY_SECTION - \"§c§lExample §c§9Text\".", + "MINIMESSAGE - \"Example Text\". (https://webui.adventure.kyori.net/)", + "GSON - \"[{\"text\":\"Example\",\"bold\":true,\"color\":\"red\"},{\"text\":\" \",\"bold\":true},{\"text\":\"Text\",\"bold\":true,\"color\":\"blue\"}]\". (https://minecraft.tools/en/json_text.php/)", + "GSON_COLOR_DOWNSAMPLING - Same as GSON, but uses downsampling." + }) + public Serializers SERIALIZER = Serializers.LEGACY_AMPERSAND; + + @Create + public MAIN MAIN; + + public static class MAIN { + + @Comment({ + "World file type:", + " SCHEMATIC (MCEdit .schematic, 1.12.2 and lower, not recommended)", + " STRUCTURE (structure block .nbt, any Minecraft version is supported, but the latest one is recommended).", + " WORLDEDIT_SCHEM (WorldEdit .schem, any Minecraft version is supported, but the latest one is recommended)." + }) + public BuiltInWorldFileType WORLD_FILE_TYPE = BuiltInWorldFileType.STRUCTURE; + public String WORLD_FILE_PATH = "world.nbt"; + + @Comment("World time in ticks (24000 ticks == 1 in-game day)") + public long WORLD_TICKS = 1000L; + + @Comment("World light level (from 0 to 15)") + public int WORLD_LIGHT_LEVEL = 15; + + @Comment("Should we override block light level (to light up the nether and the end)") + public boolean WORLD_OVERRIDE_BLOCK_LIGHT_LEVEL = true; + + @Comment("Available: ADVENTURE, CREATIVE, SURVIVAL, SPECTATOR") + public GameMode GAME_MODE = GameMode.ADVENTURE; + + @Comment( + "Available dimensions: OVERWORLD, NETHER, THE_END" + ) + public Dimension DIMENSION = Dimension.OVERWORLD; + + @Create + public HUB_COMMAND HUB_COMMAND; + + public static class HUB_COMMAND { + + public boolean REQUIRE_PERMISSION = false; + public List ALIASES = List.of("hub", "lobby"); + } + + @Comment("Message that will be sent when player joins the hub.") + public List WELCOME_MESSAGE = List.of("&7Type /menu to open menu."); + @Comment("Title that will be displayed when player joins the hub.") + public String WELCOME_TITLE = ""; + public String WELCOME_SUBTITLE = ""; + public int WELCOME_TITLE_FADE_IN_MILLIS = 100; + public int WELCOME_TITLE_STAY_MILLIS = 2000; + public int WELCOME_TITLE_FADE_OUT_MILLIS = 100; + + @Comment("Player will teleported back to the spawn position if it falls below the `y-limit` when enabled.") + public boolean ENABLE_Y_LIMIT = true; + public int Y_LIMIT = -10; + + @Comment("Try increasing this value if NPC skins are not loading.") + public int SKIN_LOAD_SECONDS = 3; + + @Create + public WORLD_COORDS WORLD_COORDS; + + public static class WORLD_COORDS { + + public int X = 0; + public int Y = 0; + public int Z = 0; + } + + @Create + public PLAYER_COORDS PLAYER_COORDS; + + public static class PLAYER_COORDS { + + public double X = 0; + public double Y = 0; + public double Z = 0; + public double YAW = 90; + public double PITCH = 0; + } + + @Comment({ + "Menu that will be opened when player joins the hub.", + "Set to blank value (default-menu: \"\") to disable." + }) + public String DEFAULT_MENU = ""; + + @Comment("List of boss bars that will be set for the player in the specified order. Set to empty list (bossbars: []) to disable.") + public List BOSSBARS = List.of( + Settings.createNodeSequence(BOSSBAR.class, false, "BossBar 1", 1.0, BossBar.Color.PINK, BossBar.Overlay.PROGRESS, 5000), + Settings.createNodeSequence(BOSSBAR.class, false, "BossBar 2", 0.5, BossBar.Color.RED, BossBar.Overlay.PROGRESS, 5000), + Settings.createNodeSequence(BOSSBAR.class, false, "BossBar 3", 0.0, BossBar.Color.BLUE, BossBar.Overlay.PROGRESS, 5000), + Settings.createNodeSequence(BOSSBAR.class, true, "", 0.0, BossBar.Color.PINK, BossBar.Overlay.PROGRESS, 5000) + ); + + public static class BOSSBAR { + + @Comment("Hides boss bar") + public boolean HIDDEN = false; + public String NAME = ""; + public double PROGRESS = 1.0; + public BossBar.Color COLOR = BossBar.Color.PINK; + public BossBar.Overlay OVERLAY = BossBar.Overlay.PROGRESS; + @Comment("Set -1 to mark this boss bar as the last one. (Next boss bar won't be displayed)") + public long STAY_TIME_MILLIS = -1; + } + + @Create + public SIDEBAR SIDEBAR; + + public static class SIDEBAR { + + public boolean ENABLED = true; + public String TITLE = "This is a sidebar."; + public List LINES = List.of("&7Configure me in config.yml."); + } + + @Comment("Commands that will be available in the hub.") + public List COMMANDS = List.of( + Settings.createNodeSequence(COMMAND.class, "menu", List.of("servers"), Settings.createNodeSequence(ACTION.class, ACTION.Type.OPEN_MENU, "menu")), + Settings.createNodeSequence(COMMAND.class, "server1", List.of(), Settings.createNodeSequence(ACTION.class, ACTION.Type.CONNECT_TO_SERVER, "server1")) + ); + + public static class COMMAND { + + public String COMMAND; + public List ALIASES; + @Comment("Action to perform when the command is executed.") + @Create + public ACTION ACTION; + } + + public List MENUS = List.of( + Settings.createNodeSequence(MENU.class, + "menu", + "Menu", + List.of( + Settings.createNodeSequence(MENU.ITEM_DATA.class, ".", "minecraft:black_stained_glass_pane", List.of("minecraft:glass_pane"), 1, false, 0, false, "", "", List.of()), + Settings.createNodeSequence(MENU.ITEM_DATA.class, "1", "minecraft:leather_helmet", List.of(), 1, true, 12544467, false, "", "&fOpen another menu", List.of()), + Settings.createNodeSequence(MENU.ITEM_DATA.class, "2", "minecraft:stone", List.of(), 2, false, 0, true, "", "&fServer1", List.of("&r&7This is a server.")), + Settings.createNodeSequence(MENU.ITEM_DATA.class, "3", "minecraft:player_head", List.of("minecraft:experience_bottle"), 1, false, 0, false, "f051234a-8c3d-45d5-8e78-df729dd0da8c;eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjcwNWZkOTRhMGM0MzE5MjdmYjRlNjM5YjBmY2ZiNDk3MTdlNDEyMjg1YTAyYjQzOWUwMTEyZGEyMmIyZTJlYyJ9fX0=", "&fWhat is this?", List.of()) + ), + List.of( + ".........", + "...123...", + "........." + ), + MENU.DEFAULT_ACTION_TYPE.DO_NOTHING, + List.of( + Settings.createNodeSequence(MENU.MENU_ACTION.class, List.of("3,1"), Settings.createNodeSequence(ACTION.class, ACTION.Type.OPEN_MENU, "menu2")), + Settings.createNodeSequence(MENU.MENU_ACTION.class, List.of("4,1"), Settings.createNodeSequence(ACTION.class, ACTION.Type.CONNECT_TO_SERVER, "server1")), + Settings.createNodeSequence(MENU.MENU_ACTION.class, List.of("5,1"), Settings.createNodeSequence(ACTION.class, ACTION.Type.SEND_MESSAGE, "&7This is a virtual hub!")) + )), + Settings.createNodeSequence(MENU.class, + "menu2", + "Another Menu", + List.of( + Settings.createNodeSequence(MENU.ITEM_DATA.class, ".", "minecraft:black_stained_glass_pane", List.of("minecraft:glass_pane"), 1, false, 0, false, "", "", List.of()), + Settings.createNodeSequence(MENU.ITEM_DATA.class, "1", "minecraft:barrier", List.of(), 1, false, 0, false, "", "&fClose menu", List.of()), + Settings.createNodeSequence(MENU.ITEM_DATA.class, "2", "minecraft:bedrock", List.of(), 1, false, 0, false, "", "&fGo back", List.of()) + ), + List.of( + ".........", + ". .", + ". 1 2 .", + ". .", + "........." + ), + MENU.DEFAULT_ACTION_TYPE.DO_NOTHING, + List.of( + Settings.createNodeSequence(MENU.MENU_ACTION.class, List.of("3,2"), Settings.createNodeSequence(ACTION.class, ACTION.Type.CLOSE_MENU, "")), + Settings.createNodeSequence(MENU.MENU_ACTION.class, List.of("5,2"), Settings.createNodeSequence(ACTION.class, ACTION.Type.OPEN_MENU, "menu")) + )) + ); + + public static class MENU { + + @Comment("Menu ID that will be used in OPEN_MENU actions.") + public String MENU_ID = "menu"; + public String TITLE = "Menu"; + @Comment("Items that will be used in `menu-contents`.") + public List ITEMS = List.of(); + + public static class ITEM_DATA { + + @Comment("Any character that will represent this item in `menu-contents`.") + public String ID = ""; + @Comment("Modern item ID, e.g. \"minecraft:stone\".") + public String ITEM = ""; + public List FALLBACK_ITEMS = List.of(); + public int COUNT = 1; + public boolean HAS_COLOR = false; + @Comment("You can get color value at https://notwoods.github.io/minecraft-tools/armorcolor/.") + public int COLOR = 0; + public boolean ENCHANTED = false; + @Comment({ + "Player skin in \"uuid;value\" format", + "You can get these values at https://mineskin.org/." + }) + public String SKULL_OWNER = ""; + public String CUSTOM_NAME = ""; + public List LORE = List.of(); + } + + @Comment({ + "Menu content. The size of the menu depends on the length and number of lines.", + "For example, 6 lines of 9 characters each represent a 9x6 menu.", + "Each character represents an item (see `item-id`); space is an empty slot." + }) + public List MENU_CONTENTS = List.of( + " ", + " ", + " " + ); + + @Comment({ + "The default action to be performed when player clicks on slot.", + "Available values: DO_NOTHING, CLOSE_MENU." + }) + public DEFAULT_ACTION_TYPE DEFAULT_ACTION = DEFAULT_ACTION_TYPE.DO_NOTHING; + + public enum DEFAULT_ACTION_TYPE { + DO_NOTHING(Settings.createNodeSequence(ACTION.class, ACTION.Type.DO_NOTHING, "")), + CLOSE_MENU(Settings.createNodeSequence(ACTION.class, ACTION.Type.CLOSE_MENU, "")); + + private final ACTION action; + + DEFAULT_ACTION_TYPE(ACTION action) { + this.action = action; + } + + public ACTION getAction() { + return this.action; + } + } + + public List ACTIONS = List.of(); + + public static class MENU_ACTION { + + @Comment("List of slots in one of the following formats: \"slotNumber\", \"column,row\".") + public List SLOTS = List.of(); + @Comment("Action to perform when player clicks on specified slots.") + @Create + public ACTION ACTION = new ACTION(); + } + } + + public List NPCS = List.of( + Settings.createNodeSequence(NPC.class, + "&7Click to open menu", + 0.0, 0.0, 5.0, 270.0, 0.0, + true, + Settings.createNodeSequence(NPC.SKIN_DATA.class, "09469aaa-422e-4717-a73f-bd530dbd6c7e", "ewogICJ0aW1lc3RhbXAiIDogMTY1NDMzNjk2MDgzNiwKICAicHJvZmlsZUlkIiA6ICIyZDlhNWExYjIzZWQ0MWRkYjgzMWMzZjM3Zjk2NzA3ZCIsCiAgInByb2ZpbGVOYW1lIiA6ICJBd2Vzb21lS2FsaW41NSIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9jOGJjZTJmMWExY2QyNWZiYTU2MWU3NjI2ODYxYmYwMzA1OGY5MGI1OTNhY2MwODE2ZWZlNGZmZGY0ZGUwNmE0IgogICAgfQogIH0KfQ==", "PsdiXxQldurw+qrNHyXRkAhPpcLeY/c8hTW7lr8S1kGFE2P4OLuQZbnjzKxLCCcudhBBDgjU/3hJNXgC1Spnz4ywT1+RVCJ/F4SQFh39Y8mQQra1dz4jM0mvTfyCttTLY/sP+g91HCirYfmS6xXcmaFW9aGhFJasJ5xwXQ9Ez9mdVgajeBDh7DxPv4dKkyQsE13XD8Z4ipgRUsDflLIF2oDF8jLSgTpnlukJdlFmJIBPcO+g8S5K1V4UVxeZFRykuu1Hqv9g0BU56A2uKYGF7xKL9xUMsvRoRl0NbdJgWtuJRmt8ybMdD+KKhIQ2TNO/7hNKkZV8t5vWXPbLRS/8Q1KtINwVtJyha1SZ0jTxbrGrFmD2VxzNJMI2KcFBINS66+zVopgAV79xIiqQEa3x7PsCPUOGbNZO8oc5DmFstiBo/ypyZERZnwAvkrxag97Q7wPQrXkEmiYQWp4d5CWg8yfg0h3PJGUT58hcw9poaLSXS9lpA4Zp1nv+BWx8U32rczTqaEKCaW3p2b9mRmEYTcGl7+GsCsbLDuBDiU/JNVEUu8bhzVDKBNGaIHR2nVWoqZG0nReMx7b11j53T3ubAj3ATLuYIyRBqysNeztACo98ynMBVLn7gXaYDhfxqtWYxvLkrYpk/JvPOwoDX2gUUFGf3i+oQbJHW6YR5brqrCw="), + Settings.createNodeSequence(ACTION.class, ACTION.Type.OPEN_MENU, "menu") + ) + ); + + public static class NPC { + + public String DISPLAY_NAME = ""; + public double X = 0; + public double Y = 0; + public double Z = 0; + public double YAW = 0; + public double PITCH = 0; + public boolean LOAD_SKIN = false; + @Comment("You can get skin properties at https://mineskin.org/.") + @Create + public SKIN_DATA SKIN_DATA; + + public static class SKIN_DATA { + + public String UUID = ""; + public String VALUE = ""; + public String SIGNATURE = ""; + @Comment({ + "(255 - all skin parts)", + "Sum of the following numbers:", + "Cape - 1", + "Jacket - 2", + "Left Sleeve - 4", + "Right Sleeve - 8", + "Left Leg - 16", + "Right Leg - 32", + "Hat - 64", + }) + public int DISPLAYED_SKIN_PARTS = 255; + } + + @Comment("Action to be performed when player clicks on NPC.") + @Create + public ACTION ACTION; + } + + public static class ACTION { + public enum Type { + DO_NOTHING, + CLOSE_MENU, + SEND_MESSAGE, + OPEN_MENU, + CONNECT_TO_SERVER, + KICK_PLAYER + } + + @Comment("Available values: DO_NOTHING, CLOSE_MENU, SEND_MESSAGE, OPEN_MENU, CONNECT_TO_SERVER, KICK_PLAYER") + public Type TYPE = Type.DO_NOTHING; + @Comment({ + "Depends on action type:", + "DO_NOTHING: Not used.", + "CLOSE_MENU: Not used.", + "SEND_MESSAGE: Message to send, lines should be separated with {NL}.", + "OPEN_MENU: Menu ID", + "CONNECT_TO_SERVER: Server name (as in velocity.toml).", + "KICK_PLAYER: Kick reason" + }) + public String DATA = ""; + } + + public List HOLOGRAMS = List.of( + Settings.createNodeSequence(HOLOGRAM.class, 5.0, 1.0, 5.0, List.of("&fThis", "&7is a", "&8hologram.")) + ); + + public static class HOLOGRAM { + + public double X = 0; + public double Y = 0; + public double Z = 0; + public List LINES = List.of(); + } + } +} diff --git a/src/main/java/net/elytrium/limbohub/command/HubCommand.java b/src/main/java/net/elytrium/limbohub/command/HubCommand.java new file mode 100644 index 0000000..d3f26b3 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/command/HubCommand.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.command; + +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.permission.Tristate; +import com.velocitypowered.api.proxy.Player; +import net.elytrium.limbohub.LimboHub; +import net.elytrium.limbohub.Settings; + +public class HubCommand implements SimpleCommand { + + private final LimboHub plugin; + + public HubCommand(LimboHub plugin) { + this.plugin = plugin; + } + + @Override + public void execute(Invocation invocation) { + if (invocation.source() instanceof Player) { + this.plugin.sendToHub((Player) invocation.source()); + } + } + + @Override + public boolean hasPermission(Invocation invocation) { + return !Settings.IMP.MAIN.HUB_COMMAND.REQUIRE_PERMISSION + || invocation.source().getPermissionValue("limbohub.command.hub") == Tristate.TRUE; + } +} diff --git a/src/main/java/net/elytrium/limbohub/command/ReloadCommand.java b/src/main/java/net/elytrium/limbohub/command/ReloadCommand.java new file mode 100644 index 0000000..9181b8b --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/command/ReloadCommand.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.command; + +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.permission.Tristate; +import net.elytrium.limbohub.LimboHub; + +public class ReloadCommand implements SimpleCommand { + + private final LimboHub plugin; + + public ReloadCommand(LimboHub plugin) { + this.plugin = plugin; + } + + + @Override + public void execute(Invocation invocation) { + this.plugin.reload(); + } + + @Override + public boolean hasPermission(Invocation invocation) { + return invocation.source().getPermissionValue("limbohub.command.reload") == Tristate.TRUE; + } +} diff --git a/src/main/java/net/elytrium/limbohub/data/LinkedBossBar.java b/src/main/java/net/elytrium/limbohub/data/LinkedBossBar.java new file mode 100644 index 0000000..a402961 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/data/LinkedBossBar.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.data; + +import net.kyori.adventure.bossbar.BossBar; + +public class LinkedBossBar { + + private final BossBar bossBar; + private final long stayTime; + private LinkedBossBar next; + + public LinkedBossBar(BossBar bossBar, long stayTime, LinkedBossBar next) { + this.bossBar = bossBar; + this.stayTime = stayTime; + this.next = next; + } + + public BossBar getBossBar() { + return this.bossBar; + } + + public long getStayTime() { + return this.stayTime; + } + + public void setNext(LinkedBossBar next) { + this.next = next; + } + + public LinkedBossBar getNext() { + return this.next; + } +} diff --git a/src/main/java/net/elytrium/limbohub/data/Sidebar.java b/src/main/java/net/elytrium/limbohub/data/Sidebar.java new file mode 100644 index 0000000..ef836e5 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/data/Sidebar.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.data; + +import java.util.List; +import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limbohub.protocol.packets.DisplayObjective; +import net.elytrium.limbohub.protocol.packets.UpdateObjectives; +import net.elytrium.limbohub.protocol.packets.UpdateScore; +import net.kyori.adventure.text.Component; + +public class Sidebar { + + private final Component title; + private final List lines; + + public Sidebar(Component title, List lines) { + this.title = title; + this.lines = lines; + } + + public void show(LimboPlayer player) { + player.writePacketAndFlush(new UpdateObjectives("sidebar_obj", (byte) 0, this.title, 0)); + player.writePacketAndFlush(new DisplayObjective((byte) 1, "sidebar_obj")); + int score = this.lines.size(); + for (Component line : this.lines) { + player.writePacketAndFlush(new UpdateScore(line, 0, "sidebar_obj", --score)); + } + } + + public Component getTitle() { + return this.title; + } + + public List getLines() { + return this.lines; + } +} diff --git a/src/main/java/net/elytrium/limbohub/entities/Hologram.java b/src/main/java/net/elytrium/limbohub/entities/Hologram.java new file mode 100644 index 0000000..83d0f98 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/entities/Hologram.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.entities; + +import java.util.List; +import java.util.UUID; +import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limbohub.LimboHub; +import net.elytrium.limbohub.protocol.entities.ArmorStand; +import net.elytrium.limbohub.protocol.packets.SetEntityMetadata; +import net.elytrium.limbohub.protocol.packets.SpawnEntity; +import net.kyori.adventure.text.Component; + +public class Hologram { + + private static final double LINE_HEIGHT = 0.27; + + public final int entityId; + public final double positionX; + public final double positionY; + public final double positionZ; + public final List lines; + + public Hologram(int entityId, double positionX, double positionY, double positionZ, List lines) { + this.entityId = entityId; + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + this.lines = lines; + } + + public Hologram(double positionX, double positionY, double positionZ, List lines) { + this(LimboHub.reserveEntityIds(lines.size()), positionX, positionY, positionZ, lines); + } + + public void spawn(LimboPlayer player) { + double hologramY = this.positionY + LINE_HEIGHT * this.lines.size() - 1.975; + int currentEntityId = this.entityId; + for (Component line : this.lines) { + hologramY -= LINE_HEIGHT; + + player.writePacketAndFlush( + new SpawnEntity(currentEntityId, UUID.randomUUID(), ArmorStand::getEntityType, this.positionX, hologramY, this.positionZ, 0, 0, 0, 0)); + player.writePacketAndFlush(new SetEntityMetadata(currentEntityId, version -> ArmorStand.buildHologramMetadata(version, line))); + + ++currentEntityId; + } + } + + public double getPositionX() { + return this.positionX; + } + + public double getPositionY() { + return this.positionY; + } + + public double getPositionZ() { + return this.positionZ; + } + + public List getLines() { + return this.lines; + } +} diff --git a/src/main/java/net/elytrium/limbohub/entities/NPC.java b/src/main/java/net/elytrium/limbohub/entities/NPC.java new file mode 100644 index 0000000..2b56a29 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/entities/NPC.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.entities; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItem; +import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfo; +import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfo; +import java.nio.charset.StandardCharsets; +import java.util.EnumSet; +import java.util.List; +import java.util.UUID; +import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limbohub.LimboHub; +import net.elytrium.limbohub.Settings; +import net.elytrium.limbohub.protocol.entities.ArmorStand; +import net.elytrium.limbohub.protocol.entities.Player; +import net.elytrium.limbohub.protocol.packets.ScoreboardTeam; +import net.elytrium.limbohub.protocol.packets.SetEntityMetadata; +import net.elytrium.limbohub.protocol.packets.SetHeadRotation; +import net.elytrium.limbohub.protocol.packets.SpawnEntity; +import net.elytrium.limbohub.protocol.packets.SpawnPlayer; +import net.kyori.adventure.text.Component; + +public class NPC { + + private final int entityId; + private final UUID uuid; + private final String username; + private final Component displayName; + private final double positionX; + private final double positionY; + private final double positionZ; + private final float yaw; + private final float pitch; + private final Settings.MAIN.NPC.SKIN_DATA skinData; + private final Settings.MAIN.ACTION action; + + public NPC(int entityId, UUID uuid, String username, Component displayName, double positionX, double positionY, + double positionZ, float yaw, float pitch, Settings.MAIN.NPC.SKIN_DATA skinData, Settings.MAIN.ACTION action) { + this.entityId = entityId; + this.uuid = uuid; + this.username = username; + this.displayName = displayName; + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + this.yaw = yaw; + this.pitch = pitch; + this.skinData = skinData; + this.action = action; + } + + public NPC(int entityId, Component displayName, double positionX, double positionY, double positionZ, + float yaw, float pitch, Settings.MAIN.NPC.SKIN_DATA skinData, Settings.MAIN.ACTION action) { + this(entityId, skinData != null ? UUID.fromString(skinData.UUID) : UUID.nameUUIDFromBytes(("NPC:" + entityId).getBytes(StandardCharsets.UTF_8)), + "_npc" + entityId, displayName, positionX, positionY, positionZ, yaw, pitch, skinData, action); + } + + public NPC(Component displayUsername, double positionX, double positionY, double positionZ, + float yaw, float pitch, Settings.MAIN.NPC.SKIN_DATA skinData, Settings.MAIN.ACTION action) { + this(LimboHub.reserveEntityIds(2), displayUsername, positionX, positionY, positionZ, yaw, pitch, skinData, action); + } + + public void spawn(LimboPlayer player) { + GameProfile profile = new GameProfile(this.uuid, this.username, List.of()); + if (this.skinData != null) { + profile = profile.withProperties(List.of(new GameProfile.Property("textures", this.skinData.VALUE, this.skinData.SIGNATURE))); + } + + if (player.getProxyPlayer().getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { + UpsertPlayerInfo.Entry entry = new UpsertPlayerInfo.Entry(this.uuid); + entry.setProfile(profile); + entry.setListed(false); + + player.writePacketAndFlush( + new UpsertPlayerInfo( + EnumSet.of(UpsertPlayerInfo.Action.ADD_PLAYER, UpsertPlayerInfo.Action.UPDATE_LISTED), + List.of(entry) + ) + ); + } else { + player.writePacketAndFlush( + new LegacyPlayerListItem( + LegacyPlayerListItem.ADD_PLAYER, + List.of( + new LegacyPlayerListItem.Item(this.uuid) + .setName(profile.getName()) + .setProperties(profile.getProperties()) + ) + ) + ); + } + + player.writePacketAndFlush(new SpawnPlayer(this.entityId, this.uuid, this.positionX, this.positionY, this.positionZ, this.yaw, this.pitch)); + player.writePacketAndFlush(new SetHeadRotation(this.entityId, this.yaw)); + player.writePacketAndFlush(new ScoreboardTeam("sb" + this.entityId, (byte) 0, "never", + "always", 0, Component.empty(), Component.empty(), List.of(this.username))); + + if (this.skinData != null) { + byte skinParts = (byte) this.skinData.DISPLAYED_SKIN_PARTS; + player.writePacketAndFlush(new SetEntityMetadata(this.entityId, version -> Player.buildSkinPartsMetadata(version, skinParts))); + } + + if (this.displayName != null) { + player.writePacketAndFlush(new SpawnEntity(this.entityId + 1, UUID.randomUUID(), ArmorStand::getEntityType, + this.positionX, this.positionY - 0.175, this.positionZ, this.pitch, this.yaw, this.yaw, 0)); + player.writePacketAndFlush(new SetEntityMetadata(this.entityId + 1, version -> ArmorStand.buildHologramMetadata(version, this.displayName))); + } + } + + public void cleanUp(LimboPlayer player) { + if (player.getProxyPlayer().getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { + player.writePacketAndFlush(new RemovePlayerInfo(List.of(this.uuid))); + } else { + player.writePacketAndFlush( + new LegacyPlayerListItem( + LegacyPlayerListItem.REMOVE_PLAYER, + List.of(new LegacyPlayerListItem.Item(this.uuid)) + ) + ); + } + } + + public int getEntityId() { + return this.entityId; + } + + public UUID getUuid() { + return this.uuid; + } + + public String getUsername() { + return this.username; + } + + public Component getDisplayName() { + return this.displayName; + } + + public double getPositionX() { + return this.positionX; + } + + public double getPositionY() { + return this.positionY; + } + + public double getPositionZ() { + return this.positionZ; + } + + public float getYaw() { + return this.yaw; + } + + public float getPitch() { + return this.pitch; + } + + public Settings.MAIN.ACTION getAction() { + return this.action; + } +} diff --git a/src/main/java/net/elytrium/limbohub/handler/HubSessionHandler.java b/src/main/java/net/elytrium/limbohub/handler/HubSessionHandler.java new file mode 100644 index 0000000..6d2908d --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/handler/HubSessionHandler.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.handler; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.scheduler.ScheduledTask; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import net.elytrium.limboapi.api.Limbo; +import net.elytrium.limboapi.api.LimboSessionHandler; +import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limbohub.LimboHub; +import net.elytrium.limbohub.Settings; +import net.elytrium.limbohub.data.LinkedBossBar; +import net.elytrium.limbohub.entities.NPC; +import net.elytrium.limbohub.menu.Menu; +import net.elytrium.limbohub.protocol.container.Container; +import net.elytrium.limbohub.protocol.item.ItemStack; +import net.elytrium.limbohub.protocol.packets.ClickContainer; +import net.elytrium.limbohub.protocol.packets.Interact; +import net.elytrium.limbohub.protocol.packets.SetContainerContent; +import net.elytrium.limbohub.protocol.packets.SetContainerSlot; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.title.Title; + +public class HubSessionHandler implements LimboSessionHandler { + + private final Player proxyPlayer; + private final LimboHub plugin; + private LimboPlayer player; + private Menu currentMenu; + private ScheduledTask bossBarTask; + private BossBar currentBossBar; + + public HubSessionHandler(Player proxyPlayer, LimboHub plugin) { + this.proxyPlayer = proxyPlayer; + this.plugin = plugin; + } + + @Override + public void onSpawn(Limbo server, LimboPlayer player) { + this.player = player; + + for (String line : Settings.IMP.MAIN.WELCOME_MESSAGE) { + this.proxyPlayer.sendMessage(LimboHub.getSerializer().deserialize(line)); + } + + if (!Settings.IMP.MAIN.WELCOME_TITLE.isBlank() || !Settings.IMP.MAIN.WELCOME_SUBTITLE.isBlank()) { + this.proxyPlayer.showTitle(Title.title( + LimboHub.getSerializer().deserialize(Settings.IMP.MAIN.WELCOME_TITLE), + LimboHub.getSerializer().deserialize(Settings.IMP.MAIN.WELCOME_SUBTITLE), + Title.Times.times( + Duration.ofMillis(Settings.IMP.MAIN.WELCOME_TITLE_FADE_IN_MILLIS), + Duration.ofMillis(Settings.IMP.MAIN.WELCOME_TITLE_STAY_MILLIS), + Duration.ofMillis(Settings.IMP.MAIN.WELCOME_TITLE_FADE_OUT_MILLIS) + ) + )); + } + + if (this.plugin.getSidebar() != null) { + this.plugin.getSidebar().show(player); + } + + if (this.plugin.getBossBar() != null) { + this.currentBossBar = BossBar.bossBar(Component.empty(), 0.0F, BossBar.Color.PINK, BossBar.Overlay.PROGRESS); + this.proxyPlayer.showBossBar(this.currentBossBar); + this.updateBossBar(this.plugin.getBossBar()); + } + + this.plugin.getHolograms().forEach(hologram -> hologram.spawn(player)); + this.plugin.getNpcs().values().forEach(npc -> npc.spawn(player)); + + if (this.plugin.getDefaultMenu() != null) { + this.currentMenu = this.plugin.getDefaultMenu(); + this.currentMenu.getContainer().open(player); + } + + this.plugin.getServer().getScheduler() + .buildTask(this.plugin, () -> this.plugin.getNpcs().values().forEach(npc -> npc.cleanUp(player))) + .delay(Settings.IMP.MAIN.SKIN_LOAD_SECONDS, TimeUnit.SECONDS) + .schedule(); + } + + private void updateBossBar(LinkedBossBar bossBar) { + if (bossBar.getBossBar() == null) { + this.proxyPlayer.hideBossBar(this.currentBossBar); + } else { + BossBar newBossBar = bossBar.getBossBar(); + this.currentBossBar.name(newBossBar.name()) + .progress(newBossBar.progress()) + .color(newBossBar.color()) + .overlay(newBossBar.overlay()); + this.proxyPlayer.showBossBar(this.currentBossBar); + } + + if (bossBar.getNext() != null && bossBar.getStayTime() != -1) { + this.bossBarTask = this.plugin.getServer().getScheduler() + .buildTask(this.plugin, () -> this.updateBossBar(bossBar.getNext())) + .delay(bossBar.getStayTime(), TimeUnit.MILLISECONDS) + .schedule(); + } + } + + @Override + public void onDisconnect() { + if (this.currentBossBar != null) { + this.proxyPlayer.hideBossBar(this.currentBossBar); + } + + if (this.bossBarTask != null) { + this.bossBarTask.cancel(); + } + } + + @Override + public void onMove(double posX, double posY, double posZ) { + if (Settings.IMP.MAIN.ENABLE_Y_LIMIT && posY < Settings.IMP.MAIN.Y_LIMIT) { + net.elytrium.limbohub.Settings.MAIN.PLAYER_COORDS coords = Settings.IMP.MAIN.PLAYER_COORDS; + this.player.teleport(coords.X, coords.Y, coords.Z, (float) coords.YAW, (float) coords.PITCH); + } + } + + @Override + public void onGeneric(Object packet) { + if (packet instanceof ClickContainer) { + ClickContainer clickContainer = (ClickContainer) packet; + + if (this.currentMenu == null) { + return; + } + + Container container = this.currentMenu.getContainer(); + int containerId = container.getId(); + if (containerId != clickContainer.getWindowId()) { + return; + } + + if (this.proxyPlayer.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_17) <= 0) { + this.player.writePacketAndFlush(new SetContainerSlot(-1, -1, ItemStack.EMPTY)); + } + + this.player.writePacketAndFlush(new SetContainerSlot(0, 45, ItemStack.EMPTY)); + this.player.writePacketAndFlush(new SetContainerContent(null)); + + int slot = clickContainer.getSlot(); + + if (slot < 0 || slot >= container.getContents().length) { + this.player.writePacketAndFlush(new SetContainerContent(container)); + return; + } + + Settings.MAIN.ACTION action = this.currentMenu.getAction(slot); + switch (action.TYPE) { + case DO_NOTHING: + this.player.writePacketAndFlush(new SetContainerContent(container)); + break; + + case OPEN_MENU: + this.handleAction(action); + break; + + default: + container.close(this.player); + this.currentMenu = null; + this.handleAction(action); + break; + } + } else if (packet instanceof Interact) { + Interact interact = (Interact) packet; + + NPC npc = this.plugin.getNpcs().get(interact.getEntityId()); + if (npc == null) { + npc = this.plugin.getNpcs().get(interact.getEntityId() - 1); + } + + if (npc != null) { + this.handleAction(npc.getAction()); + } + } + } + + @Override + public void onChat(String chat) { + if (!chat.startsWith("/")) { + return; + } + + Settings.MAIN.ACTION action = this.plugin.getCommands().get(chat.substring(1)); + if (action == null) { + return; + } + + this.handleAction(action); + } + + private void handleAction(Settings.MAIN.ACTION action) { + switch (action.TYPE) { + case DO_NOTHING: + case CLOSE_MENU: + break; + + case SEND_MESSAGE: + for (String line : action.DATA.split("\n")) { + this.proxyPlayer.sendMessage(LimboHub.getSerializer().deserialize(line)); + } + break; + + case OPEN_MENU: + this.currentMenu = this.plugin.getMenus().get(action.DATA); + this.currentMenu.getContainer().open(this.player); + break; + + case CONNECT_TO_SERVER: + RegisteredServer server = this.plugin.getServer().getServer(action.DATA).orElseThrow(); + this.player.disconnect(server); + break; + + case KICK_PLAYER: + this.proxyPlayer.disconnect(LimboHub.getSerializer().deserialize(action.DATA)); + break; + + default: + throw new IllegalStateException(); + } + } +} diff --git a/src/main/java/net/elytrium/limbohub/listener/HubListener.java b/src/main/java/net/elytrium/limbohub/listener/HubListener.java new file mode 100644 index 0000000..b884f84 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/listener/HubListener.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.listener; + +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.proxy.Player; +import net.elytrium.limboapi.api.event.LoginLimboRegisterEvent; +import net.elytrium.limbohub.LimboHub; + +public class HubListener { + + private final LimboHub plugin; + + public HubListener(LimboHub plugin) { + this.plugin = plugin; + } + + @Subscribe(order = PostOrder.LAST) + public void onLogin(LoginLimboRegisterEvent event) { + Player player = event.getPlayer(); + event.addOnJoinCallback(() -> this.plugin.sendToHub(player)); + } +} diff --git a/src/main/java/net/elytrium/limbohub/menu/Menu.java b/src/main/java/net/elytrium/limbohub/menu/Menu.java new file mode 100644 index 0000000..e759b35 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/menu/Menu.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.menu; + +import java.util.Map; +import net.elytrium.limbohub.Settings; +import net.elytrium.limbohub.protocol.container.Container; + +public class Menu { + + private final Container container; + private final Map actions; + private final Settings.MAIN.ACTION defaultAction; + + public Menu(Container container, Map actions, Settings.MAIN.ACTION defaultAction) { + this.container = container; + this.actions = actions; + this.defaultAction = defaultAction; + } + + public Settings.MAIN.ACTION getAction(int slot) { + return this.actions.getOrDefault(slot, this.defaultAction); + } + + public Settings.MAIN.ACTION getAction(int column, int row) { + return this.getAction(row * this.container.getType().getColumns() + column); + } + + public Container getContainer() { + return this.container; + } + + public Map getActions() { + return this.actions; + } + + public Settings.MAIN.ACTION getDefaultAction() { + return this.defaultAction; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/container/Container.java b/src/main/java/net/elytrium/limbohub/protocol/container/Container.java new file mode 100644 index 0000000..a4fee16 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/container/Container.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.container; + +import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limbohub.protocol.item.ItemStack; +import net.elytrium.limbohub.protocol.packets.CloseContainer; +import net.elytrium.limbohub.protocol.packets.OpenScreen; +import net.elytrium.limbohub.protocol.packets.SetContainerContent; +import net.kyori.adventure.text.Component; + +public class Container { + + private static int ID_COUNTER = 0; + + private final int id; + private final ContainerType type; + private final Component title; + private final ItemStack[] contents; + + public Container(int id, ContainerType type, Component title, ItemStack[] contents) { + this.id = id; + this.type = type; + this.title = title; + this.contents = contents; + } + + public void open(LimboPlayer player) { + player.writePacket(new OpenScreen(this)); + player.writePacket(new SetContainerContent(this)); + player.flushPackets(); + } + + public void close(LimboPlayer player) { + player.writePacketAndFlush(new CloseContainer(this)); + } + + public void setItem(int index, ItemStack item) { + this.contents[index] = item; + } + + public void setItem(int column, int row, ItemStack item) { + this.contents[row * this.type.getColumns() + column] = item; + } + + public void setItems(ItemStack[] items) { + System.arraycopy(items, 0, this.contents, 0, Math.min(items.length, this.contents.length)); + } + + public int getId() { + return this.id; + } + + public ContainerType getType() { + return this.type; + } + + public Component getTitle() { + return this.title; + } + + public ItemStack[] getContents() { + return this.contents; + } + + public static Container genericContainer(int columns, int rows, Component title) { + return new Container(++ID_COUNTER, ContainerType.generic(columns, rows), title, new ItemStack[rows * columns]); + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/container/ContainerType.java b/src/main/java/net/elytrium/limbohub/protocol/container/ContainerType.java new file mode 100644 index 0000000..61cd657 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/container/ContainerType.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.container; + +public enum ContainerType { + GENERIC_9X1(9, 1), + GENERIC_9X2(9, 2), + GENERIC_9X3(9, 3), + GENERIC_9X4(9, 4), + GENERIC_9X5(9, 5), + GENERIC_9X6(9, 6), + GENERIC_3X3(3, 3); + + private final int columns; + private final int rows; + + ContainerType(int columns, int rows) { + this.columns = columns; + this.rows = rows; + } + + public int getColumns() { + return this.columns; + } + + public int getRows() { + return this.rows; + } + + public static ContainerType generic(int columns, int rows) { + return ContainerType.valueOf("GENERIC_" + columns + "X" + rows); + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/entities/ArmorStand.java b/src/main/java/net/elytrium/limbohub/protocol/entities/ArmorStand.java new file mode 100644 index 0000000..5a770cd --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/entities/ArmorStand.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.entities; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.Map; +import net.elytrium.limbohub.protocol.metadata.EntityMetadata; +import net.elytrium.limbohub.protocol.metadata.EntityMetadataBooleanEntry; +import net.elytrium.limbohub.protocol.metadata.EntityMetadataByteEntry; +import net.elytrium.limbohub.protocol.metadata.EntityMetadataOptionalComponentEntry; +import net.elytrium.limbohub.protocol.metadata.EntityMetadataStringEntry; +import net.kyori.adventure.text.Component; + +public class ArmorStand { + + public static int getEntityType(ProtocolVersion version) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { + return 2; + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { + return 1; + } else { + return 78; + } + } + + public static EntityMetadata buildHologramMetadata(ProtocolVersion version, Component displayName) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { + return new EntityMetadata(Map.of( + (byte) 0, new EntityMetadataByteEntry((byte) 0x20), + (byte) 2, new EntityMetadataOptionalComponentEntry(displayName), + (byte) 3, new EntityMetadataBooleanEntry(true) + )); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { + return new EntityMetadata(Map.of( + (byte) 0, new EntityMetadataByteEntry((byte) 0x20), + (byte) 2, new EntityMetadataStringEntry(displayName), + (byte) 3, new EntityMetadataBooleanEntry(true) + )); + } else { + return new EntityMetadata(Map.of( + (byte) 0, new EntityMetadataByteEntry((byte) 0x20), + (byte) 2, new EntityMetadataStringEntry(displayName), + (byte) 3, new EntityMetadataByteEntry((byte) 1) + )); + } + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/entities/Player.java b/src/main/java/net/elytrium/limbohub/protocol/entities/Player.java new file mode 100644 index 0000000..1de42ba --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/entities/Player.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.entities; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.Map; +import net.elytrium.limbohub.protocol.metadata.EntityMetadata; +import net.elytrium.limbohub.protocol.metadata.EntityMetadataByteEntry; + +public class Player { + + public static EntityMetadata buildSkinPartsMetadata(ProtocolVersion version, byte skinParts) { + if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { + return new EntityMetadata(Map.of((byte) 17, new EntityMetadataByteEntry(skinParts))); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) { + return new EntityMetadata(Map.of((byte) 16, new EntityMetadataByteEntry(skinParts))); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { + return new EntityMetadata(Map.of((byte) 15, new EntityMetadataByteEntry(skinParts))); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_10) >= 0) { + return new EntityMetadata(Map.of((byte) 13, new EntityMetadataByteEntry(skinParts))); + } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { + return new EntityMetadata(Map.of((byte) 12, new EntityMetadataByteEntry(skinParts))); + } else { + return new EntityMetadata(Map.of((byte) 10, new EntityMetadataByteEntry(skinParts))); + } + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/item/ItemStack.java b/src/main/java/net/elytrium/limbohub/protocol/item/ItemStack.java new file mode 100644 index 0000000..9e15211 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/item/ItemStack.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.item; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.elytrium.limboapi.api.material.VirtualItem; +import net.elytrium.limbohub.protocol.item.meta.ItemMeta; + +public class ItemStack { + + public static final ItemStack EMPTY = new ItemStack(); + + private final boolean present; + private final List items; + private final int count; + private final int data; + private final ItemMeta meta; + + public ItemStack(boolean present, List items, int count, int data, ItemMeta meta) { + this.present = present; + this.items = items; + this.count = count; + this.data = data; + this.meta = meta; + } + + public ItemStack(List item, int count, int data, ItemMeta meta) { + this(true, item, count, data, meta); + } + + public ItemStack(VirtualItem item, List fallback, int count, int data, ItemMeta meta) { + this(Stream.concat(Stream.of(item), fallback.stream()).collect(Collectors.toUnmodifiableList()), count, data, meta); + } + + public ItemStack() { + this(false, null, 0, 0, null); + } + + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) >= 0) { + buf.writeBoolean(this.present); + } + + if (!this.present && protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) < 0) { + buf.writeShort(-1); + } + + if (this.present) { + VirtualItem item = this.items.stream() + .dropWhile(i -> !i.isSupportedOn(protocolVersion)) + .findFirst().orElseThrow(); + + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) < 0) { + buf.writeShort(item.getID(protocolVersion)); + } else { + ProtocolUtils.writeVarInt(buf, item.getID(protocolVersion)); + } + buf.writeByte(this.count); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { + buf.writeShort(this.data); + } + + if (this.meta == null) { + buf.writeByte(0); + } else { + ProtocolUtils.writeCompoundTag(buf, this.meta.buildNbt(protocolVersion)); + } + } + } + + public boolean isPresent() { + return this.present; + } + + public List getItems() { + return this.items; + } + + public int getCount() { + return this.count; + } + + public int getData() { + return this.data; + } + + public ItemMeta getMeta() { + return this.meta; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/item/meta/AbstractItemMeta.java b/src/main/java/net/elytrium/limbohub/protocol/item/meta/AbstractItemMeta.java new file mode 100644 index 0000000..26de37a --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/item/meta/AbstractItemMeta.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.item.meta; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public abstract class AbstractItemMeta implements ItemMeta { + + private final boolean hasColor; + private final int color; + private final boolean enchanted; + private final String skullOwner; + + public AbstractItemMeta(boolean hasColor, int color, boolean enchanted, String skullOwner) { + this.hasColor = hasColor; + this.color = color; + this.enchanted = enchanted; + this.skullOwner = skullOwner; + } + + @Override + public CompoundBinaryTag buildNbt(ProtocolVersion protocolVersion) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + + GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(protocolVersion); + + Component name = this.getName(); + List lore = this.getLore(); + + CompoundBinaryTag.Builder displayBuilder = CompoundBinaryTag.builder(); + + if (name != null) { + ComponentSerializer nameSerializer = + protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0 + ? serializer : LegacyComponentSerializer.legacySection(); + + displayBuilder.putString("Name", nameSerializer.serialize(name)); + } + + if (lore != null && !lore.isEmpty()) { + ComponentSerializer loreSerializer = + protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0 + ? serializer : LegacyComponentSerializer.legacySection(); + + ListBinaryTag.Builder loreBuilder = ListBinaryTag.builder(BinaryTagTypes.STRING); + this.getLore().forEach(component -> loreBuilder.add(StringBinaryTag.of(loreSerializer.serialize(component)))); + displayBuilder.put("Lore", loreBuilder.build()); + } + + if (this.hasColor) { + displayBuilder.putInt("color", this.color); + } + + builder.put("display", displayBuilder.build()); + + builder.putInt("HideFlags", 255); + + if (this.enchanted) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { + builder.put("Enchantments", + ListBinaryTag.builder(BinaryTagTypes.COMPOUND) + .add(CompoundBinaryTag.builder() + .putString("id", "minecraft:sharpness") + .putShort("lvl", (short) 1) + .build()) + .build() + ); + } else { + builder.put("ench", + ListBinaryTag.builder(BinaryTagTypes.COMPOUND) + .add(CompoundBinaryTag.builder() + .putInt("id", 0) + .putShort("lvl", (short) 1) + .build()) + .build() + ); + } + } + + if (this.skullOwner != null && !this.skullOwner.isBlank()) { + String[] data = this.skullOwner.split(";", 2); + + int[] id; + if (this.skullOwner.contains(",")) { + id = Stream.of(this.skullOwner.split(",")).mapToInt(Integer::parseInt).toArray(); + if (id.length != 4) { + throw new IllegalArgumentException("Invalid id array length"); + } + } else { + id = new int[4]; + + UUID uuid = UUID.fromString(data[0]); + ByteBuffer buffer = ByteBuffer.allocateDirect(16); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + buffer.flip(); + buffer.asIntBuffer().get(id); + } + + builder.put("SkullOwner", + CompoundBinaryTag.builder() + .putIntArray("Id", id) + .putString("Name", "npc" + ThreadLocalRandom.current().nextInt()) + .put("Properties", + CompoundBinaryTag.builder() + .put("textures", + ListBinaryTag.builder(BinaryTagTypes.COMPOUND) + .add(CompoundBinaryTag.builder() + .putString("Value", data[1]) + .build()) + .build()) + .build()) + .build() + ); + } + + return builder.build(); + } + + public abstract Component getName(); + + public abstract List getLore(); + + public int getColor() { + return this.color; + } + + public boolean isEnchanted() { + return this.enchanted; + } + + public String getSkullOwner() { + return this.skullOwner; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/item/meta/ItemMeta.java b/src/main/java/net/elytrium/limbohub/protocol/item/meta/ItemMeta.java new file mode 100644 index 0000000..77b1e78 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/item/meta/ItemMeta.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.item.meta; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.kyori.adventure.nbt.CompoundBinaryTag; + +public interface ItemMeta { + + CompoundBinaryTag buildNbt(ProtocolVersion protocolVersion); +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/item/meta/StaticItemMeta.java b/src/main/java/net/elytrium/limbohub/protocol/item/meta/StaticItemMeta.java new file mode 100644 index 0000000..182f459 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/item/meta/StaticItemMeta.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.item.meta; + +import java.util.List; +import net.kyori.adventure.text.Component; + +public class StaticItemMeta extends AbstractItemMeta { + + private final Component name; + private final List lore; + + public StaticItemMeta(Component name, List lore, boolean hasColor, int color, boolean enchanted, String skullOwner) { + super(hasColor, color, enchanted, skullOwner); + this.name = name; + this.lore = lore; + } + + @Override + public Component getName() { + return this.name; + } + + @Override + public List getLore() { + return this.lore; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadata.java b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadata.java new file mode 100644 index 0000000..03bae07 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadata.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.metadata; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.Map; + +public class EntityMetadata { + + public static final EntityMetadata EMPTY = new EntityMetadata(Map.of()); + public static final EntityMetadata DUMMY_METADATA = new EntityMetadata(Map.of((byte) 0, new EntityMetadataByteEntry((byte) 0))); + + private final Map entries; + + public EntityMetadata(Map entries) { + this.entries = entries; + } + + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) <= 0) { + this.entries.forEach((index, value) -> { + buf.writeByte((index & 0x1F) | (value.getType(protocolVersion) << 5)); + value.encode(buf, protocolVersion); + }); + buf.writeByte(0x7F); + } else { + this.entries.forEach((index, value) -> { + buf.writeByte(index); + ProtocolUtils.writeVarInt(buf, value.getType(protocolVersion)); + value.encode(buf, protocolVersion); + }); + buf.writeByte(0xFF); + } + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataBooleanEntry.java b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataBooleanEntry.java new file mode 100644 index 0000000..d944dd6 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataBooleanEntry.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.metadata; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +public class EntityMetadataBooleanEntry implements EntityMetadataEntry { + + private final boolean value; + + public EntityMetadataBooleanEntry(boolean value) { + this.value = value; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { + buf.writeBoolean(this.value); + } + + @Override + public int getType(ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { + return 8; + } else if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { + return 7; + } else { + return 6; + } + } + + public boolean getValue() { + return this.value; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataByteEntry.java b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataByteEntry.java new file mode 100644 index 0000000..59deba6 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataByteEntry.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.metadata; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +public class EntityMetadataByteEntry implements EntityMetadataEntry { + + private final byte value; + + public EntityMetadataByteEntry(byte value) { + this.value = value; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { + buf.writeByte(this.value); + } + + @Override + public int getType(ProtocolVersion protocolVersion) { + return 0; + } + + public byte getValue() { + return this.value; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataEntry.java b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataEntry.java new file mode 100644 index 0000000..834d1ac --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataEntry.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.metadata; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +public interface EntityMetadataEntry { + + void encode(ByteBuf buf, ProtocolVersion protocolVersion); + + int getType(ProtocolVersion protocolVersion); +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataOptionalComponentEntry.java b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataOptionalComponentEntry.java new file mode 100644 index 0000000..e64581a --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataOptionalComponentEntry.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.metadata; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.Optional; +import net.kyori.adventure.text.Component; + +public class EntityMetadataOptionalComponentEntry implements EntityMetadataEntry { + + private final Component component; + + public EntityMetadataOptionalComponentEntry(Component component) { + this.component = component; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { + buf.writeBoolean(this.component != null); + if (this.component != null) { + ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(this.component)); + } + } + + @Override + public int getType(ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) >= 0) { + return 6; + } else if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { + return 5; + } else { + throw new IllegalStateException(); + } + } + + public Optional getComponent() { + return Optional.ofNullable(this.component); + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataStringEntry.java b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataStringEntry.java new file mode 100644 index 0000000..09c9cb2 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/metadata/EntityMetadataStringEntry.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.metadata; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class EntityMetadataStringEntry implements EntityMetadataEntry { + + private final String value; + + public EntityMetadataStringEntry(String value) { + this.value = value; + } + + public EntityMetadataStringEntry(Component component) { + this(LegacyComponentSerializer.legacySection().serialize(component)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, this.value); + } + + @Override + public int getType(ProtocolVersion protocolVersion) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { + return 3; + } else { + return 4; + } + } + + public String getValue() { + return this.value; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/ClickContainer.java b/src/main/java/net/elytrium/limbohub/protocol/packets/ClickContainer.java new file mode 100644 index 0000000..6b91a56 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/ClickContainer.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class ClickContainer implements MinecraftPacket { + + private int windowId; + private int stateId; + private short slot; + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + this.windowId = buf.readUnsignedByte(); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { + this.stateId = ProtocolUtils.readVarInt(buf); + } + this.slot = buf.readShort(); + buf.skipBytes(buf.readableBytes()); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + handler.handleGeneric(this); + return true; + } + + public int getWindowId() { + return this.windowId; + } + + public int getStateId() { + return this.stateId; + } + + public short getSlot() { + return this.slot; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/CloseContainer.java b/src/main/java/net/elytrium/limbohub/protocol/packets/CloseContainer.java new file mode 100644 index 0000000..97ce4ff --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/CloseContainer.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limbohub.protocol.container.Container; + +public class CloseContainer implements MinecraftPacket { + + private final int windowId; + + public CloseContainer(int windowId) { + this.windowId = windowId; + } + + public CloseContainer(Container container) { + this(container.getId()); + } + + public CloseContainer() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeByte(this.windowId); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/DisplayObjective.java b/src/main/java/net/elytrium/limbohub/protocol/packets/DisplayObjective.java new file mode 100644 index 0000000..b247e14 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/DisplayObjective.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class DisplayObjective implements MinecraftPacket { + + private final byte position; + private final String scoreName; + + public DisplayObjective(byte position, String scoreName) { + this.position = position; + this.scoreName = scoreName; + } + + public DisplayObjective() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeByte(this.position); + ProtocolUtils.writeString(buf, this.scoreName); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public byte getPosition() { + return this.position; + } + + public String getScoreName() { + return this.scoreName; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/Interact.java b/src/main/java/net/elytrium/limbohub/protocol/packets/Interact.java new file mode 100644 index 0000000..7ed2135 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/Interact.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class Interact implements MinecraftPacket { + + private int entityId; + private int type; + private float targetX; + private float targetY; + private float targetZ; + private int hand; + private boolean sneaking; + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + this.entityId = ProtocolUtils.readVarInt(buf); + this.type = ProtocolUtils.readVarInt(buf); + if (this.type == 2) { + this.targetX = buf.readFloat(); + this.targetY = buf.readFloat(); + this.targetZ = buf.readFloat(); + } + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) > 0) { + if (this.type == 0 || this.type == 2) { + this.hand = ProtocolUtils.readVarInt(buf); + } + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_15_2) > 0) { + this.sneaking = buf.readBoolean(); + } + } + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + handler.handleGeneric(this); + return true; + } + + public int getEntityId() { + return this.entityId; + } + + public int getType() { + return this.type; + } + + public float getTargetX() { + return this.targetX; + } + + public float getTargetY() { + return this.targetY; + } + + public float getTargetZ() { + return this.targetZ; + } + + public int getHand() { + return this.hand; + } + + public boolean isSneaking() { + return this.sneaking; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/OpenScreen.java b/src/main/java/net/elytrium/limbohub/protocol/packets/OpenScreen.java new file mode 100644 index 0000000..8e67b6d --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/OpenScreen.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limbohub.protocol.container.Container; + +public class OpenScreen implements MinecraftPacket { + + private final Container container; + + public OpenScreen(Container container) { + this.container = container; + } + + public OpenScreen() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeByte(this.container.getId()); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { + ProtocolUtils.writeVarInt(buf, this.container.getType().ordinal()); + } else { + ProtocolUtils.writeString(buf, "minecraft:chest"); + } + ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(this.container.getTitle())); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) { + buf.writeByte(this.container.getContents().length); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public Container getContainer() { + return this.container; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/ScoreboardTeam.java b/src/main/java/net/elytrium/limbohub/protocol/packets/ScoreboardTeam.java new file mode 100644 index 0000000..996dcd8 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/ScoreboardTeam.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class ScoreboardTeam implements MinecraftPacket { + + private final String teamName; + private final byte flags; + private final String nameTagVisibility; + private final String collisionRule; + private final int teamColor; + private final Component teamPrefix; + private final Component teamSuffix; + private final List entities; + + public ScoreboardTeam(String teamName, byte flags, String nameTagVisibility, String collisionRule, + int teamColor, Component teamPrefix, Component teamSuffix, List entities) { + this.teamName = teamName; + this.flags = flags; + this.nameTagVisibility = nameTagVisibility; + this.collisionRule = collisionRule; + this.teamColor = teamColor; + this.teamPrefix = teamPrefix; + this.teamSuffix = teamSuffix; + this.entities = entities; + } + + public ScoreboardTeam() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, this.teamName); + buf.writeByte(0); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { + GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(protocolVersion); + ProtocolUtils.writeString(buf, serializer.serialize(Component.empty())); + buf.writeByte(this.flags); + ProtocolUtils.writeString(buf, this.nameTagVisibility); + ProtocolUtils.writeString(buf, this.collisionRule); + ProtocolUtils.writeVarInt(buf, this.teamColor); + ProtocolUtils.writeString(buf, serializer.serialize(this.teamPrefix)); + ProtocolUtils.writeString(buf, serializer.serialize(this.teamSuffix)); + } else { + LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); + buf.writeByte(0); + ProtocolUtils.writeString(buf, serializer.serialize(this.teamPrefix)); + ProtocolUtils.writeString(buf, serializer.serialize(this.teamSuffix)); + buf.writeByte(this.flags); + ProtocolUtils.writeString(buf, this.nameTagVisibility); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { + ProtocolUtils.writeString(buf, this.collisionRule); + } + ProtocolUtils.writeVarInt(buf, this.teamColor); + } + ProtocolUtils.writeVarInt(buf, this.entities.size()); + this.entities.forEach(entity -> ProtocolUtils.writeString(buf, entity)); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public String getTeamName() { + return this.teamName; + } + + public byte getFlags() { + return this.flags; + } + + public String getNameTagVisibility() { + return this.nameTagVisibility; + } + + public String getCollisionRule() { + return this.collisionRule; + } + + public int getTeamColor() { + return this.teamColor; + } + + public Component getTeamPrefix() { + return this.teamPrefix; + } + + public Component getTeamSuffix() { + return this.teamSuffix; + } + + public List getEntities() { + return this.entities; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerContent.java b/src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerContent.java new file mode 100644 index 0000000..22d9462 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerContent.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limbohub.protocol.container.Container; +import net.elytrium.limbohub.protocol.item.ItemStack; + +public class SetContainerContent implements MinecraftPacket { + + private final Container container; + + public SetContainerContent(Container container) { + this.container = container; + } + + public SetContainerContent() { + throw new IllegalArgumentException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeByte(this.container != null ? this.container.getId() : 0); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { + buf.writeByte(0); + } + if (this.container != null) { + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { + ProtocolUtils.writeVarInt(buf, this.container.getContents().length); + } else { + buf.writeShort(this.container.getContents().length); + } + for (ItemStack item : this.container.getContents()) { + item.encode(buf, protocolVersion); + } + } else { + int slots = 45; + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_16_4) <= 0) { + slots = 46; + } + + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { + ProtocolUtils.writeVarInt(buf, slots); + } else { + buf.writeShort(slots); + } + for (int i = 0; i < slots; ++i) { + ItemStack.EMPTY.encode(buf, protocolVersion); + } + } + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { + ItemStack.EMPTY.encode(buf, protocolVersion); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public Container getContainer() { + return this.container; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerSlot.java b/src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerSlot.java new file mode 100644 index 0000000..658a366 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/SetContainerSlot.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limbohub.protocol.container.Container; +import net.elytrium.limbohub.protocol.item.ItemStack; + +public class SetContainerSlot implements MinecraftPacket { + + private final int window; + private final int slot; + private final ItemStack item; + + public SetContainerSlot(int window, int slot, ItemStack item) { + this.window = window; + this.slot = slot; + this.item = item; + } + + public SetContainerSlot(Container container, int slot) { + this(container.getId(), slot, container.getContents()[slot]); + } + + public SetContainerSlot() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + buf.writeByte(this.window); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { + buf.writeByte(0); + } + buf.writeShort(this.slot); + this.item.encode(buf, protocolVersion); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public int getWindow() { + return this.window; + } + + public int getSlot() { + return this.slot; + } + + public ItemStack getItem() { + return this.item; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/SetEntityMetadata.java b/src/main/java/net/elytrium/limbohub/protocol/packets/SetEntityMetadata.java new file mode 100644 index 0000000..2afcb44 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/SetEntityMetadata.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.function.Function; +import net.elytrium.limbohub.protocol.metadata.EntityMetadata; + +public class SetEntityMetadata implements MinecraftPacket { + + private final int entityId; + private final Function metadata; + + public SetEntityMetadata(int entityId, Function metadata) { + this.entityId = entityId; + this.metadata = metadata; + } + + public SetEntityMetadata(int entityId, EntityMetadata metadata) { + this(entityId, protocolVersion -> metadata); + } + + public SetEntityMetadata() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.entityId); + this.metadata.apply(protocolVersion).encode(buf, protocolVersion); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/SetHeadRotation.java b/src/main/java/net/elytrium/limbohub/protocol/packets/SetHeadRotation.java new file mode 100644 index 0000000..bd2b1df --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/SetHeadRotation.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class SetHeadRotation implements MinecraftPacket { + + private final int entityId; + private final float yaw; + + public SetHeadRotation(int entityId, float yaw) { + this.entityId = entityId; + this.yaw = yaw; + } + + public SetHeadRotation() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.entityId); + buf.writeByte((int) (this.yaw * (256.0F / 360.0F))); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public int getEntityId() { + return this.entityId; + } + + public float getYaw() { + return this.yaw; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/SpawnEntity.java b/src/main/java/net/elytrium/limbohub/protocol/packets/SpawnEntity.java new file mode 100644 index 0000000..be2d144 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/SpawnEntity.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.UUID; +import java.util.function.Function; + +public class SpawnEntity implements MinecraftPacket { + + private final int id; + private final UUID uuid; + private final Function type; + private final double positionX; + private final double positionY; + private final double positionZ; + private final float pitch; + private final float yaw; + private final float headYaw; + private final int data; + + public SpawnEntity(int id, UUID uuid, Function type, double positionX, + double positionY, double positionZ, float pitch, float yaw, float headYaw, int data) { + this.id = id; + this.uuid = uuid; + this.type = type; + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + this.pitch = pitch; + this.yaw = yaw; + this.headYaw = headYaw; + this.data = data; + } + + public SpawnEntity() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.id); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) > 0) { + ProtocolUtils.writeUuid(buf, this.uuid); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) > 0) { + ProtocolUtils.writeVarInt(buf, this.type.apply(protocolVersion)); + } else { + buf.writeByte(this.type.apply(protocolVersion)); + } + buf.writeDouble(this.positionX); + buf.writeDouble(this.positionY); + buf.writeDouble(this.positionZ); + } else { + buf.writeByte(this.type.apply(protocolVersion)); + buf.writeInt((int) (this.positionX * 32.0)); + buf.writeInt((int) (this.positionY * 32.0)); + buf.writeInt((int) (this.positionZ * 32.0)); + } + buf.writeByte((int) (this.pitch * (256.0F / 360.0F))); + buf.writeByte((int) (this.yaw * (256.0F / 360.0F))); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_18_2) > 0) { + buf.writeByte((int) (this.headYaw * (256.0F / 360.0F))); + ProtocolUtils.writeVarInt(buf, this.data); + } else { + buf.writeInt(this.data); + } + if (this.data != 0 || protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { + buf.writeShort(0); + buf.writeShort(0); + buf.writeShort(0); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/SpawnPlayer.java b/src/main/java/net/elytrium/limbohub/protocol/packets/SpawnPlayer.java new file mode 100644 index 0000000..9f53746 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/SpawnPlayer.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.UUID; +import net.elytrium.limbohub.protocol.metadata.EntityMetadata; + +public class SpawnPlayer implements MinecraftPacket { + + private final int entityId; + private final UUID entityUuid; + private final double positionX; + private final double positionY; + private final double positionZ; + private final float yaw; + private final float pitch; + + public SpawnPlayer(int entityId, UUID entityUuid, double positionX, + double positionY, double positionZ, float yaw, float pitch) { + this.entityId = entityId; + this.entityUuid = entityUuid; + this.positionX = positionX; + this.positionY = positionY; + this.positionZ = positionZ; + this.yaw = yaw; + this.pitch = pitch; + } + + public SpawnPlayer() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.entityId); + ProtocolUtils.writeUuid(buf, this.entityUuid); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { + buf.writeDouble(this.positionX); + buf.writeDouble(this.positionY); + buf.writeDouble(this.positionZ); + } else { + buf.writeInt((int) (this.positionX * 32.0D)); + buf.writeInt((int) (this.positionY * 32.0D)); + buf.writeInt((int) (this.positionZ * 32.0D)); + } + buf.writeByte((int) (this.yaw * (256.0F / 360.0F))); + buf.writeByte((int) (this.pitch * (256.0F / 360.0F))); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) <= 0) { + buf.writeShort(0); + } + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_14_4) <= 0) { + EntityMetadata.DUMMY_METADATA.encode(buf, protocolVersion); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public int getEntityId() { + return this.entityId; + } + + public UUID getEntityUuid() { + return this.entityUuid; + } + + public double getPositionX() { + return this.positionX; + } + + public double getPositionY() { + return this.positionY; + } + + public double getPositionZ() { + return this.positionZ; + } + + public float getYaw() { + return this.yaw; + } + + public float getPitch() { + return this.pitch; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/UpdateObjectives.java b/src/main/java/net/elytrium/limbohub/protocol/packets/UpdateObjectives.java new file mode 100644 index 0000000..508cf56 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/UpdateObjectives.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class UpdateObjectives implements MinecraftPacket { + + private final String objectiveName; + private final byte mode; + private final Component objectiveValue; + private final int type; + + public UpdateObjectives(String objectiveName, byte mode, Component objectiveValue, int type) { + this.objectiveName = objectiveName; + this.mode = mode; + this.objectiveValue = objectiveValue; + this.type = type; + } + + public UpdateObjectives() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeString(buf, this.objectiveName); + buf.writeByte(this.mode); + if (this.mode == 0 || this.mode == 2) { + ComponentSerializer serializer = protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0 + ? ProtocolUtils.getJsonChatSerializer(protocolVersion) : LegacyComponentSerializer.legacySection(); + ProtocolUtils.writeString(buf, serializer.serialize(this.objectiveValue)); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0) { + ProtocolUtils.writeVarInt(buf, this.type); + } else { + if (this.type == 0) { + ProtocolUtils.writeString(buf, "integer"); + } else if (this.type == 1) { + ProtocolUtils.writeString(buf, "hearts"); + } else { + throw new IllegalArgumentException("Invalid type: " + this.type); + } + } + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public String getObjectiveName() { + return this.objectiveName; + } + + public byte getMode() { + return this.mode; + } + + public Component getObjectiveValue() { + return this.objectiveValue; + } + + public int getType() { + return this.type; + } +} diff --git a/src/main/java/net/elytrium/limbohub/protocol/packets/UpdateScore.java b/src/main/java/net/elytrium/limbohub/protocol/packets/UpdateScore.java new file mode 100644 index 0000000..9d2f872 --- /dev/null +++ b/src/main/java/net/elytrium/limbohub/protocol/packets/UpdateScore.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub.protocol.packets; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +public class UpdateScore implements MinecraftPacket { + + private final Component entityName; + private final int action; + private final String objectiveName; + private final int value; + + public UpdateScore(Component entityName, int action, String objectiveName, int value) { + this.entityName = entityName; + this.action = action; + this.objectiveName = objectiveName; + this.value = value; + } + + public UpdateScore() { + throw new IllegalStateException(); + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + String serializedName = LegacyComponentSerializer.legacySection().serialize(this.entityName); + if (serializedName.length() > 40) { + serializedName = serializedName.substring(0, 40); + } + ProtocolUtils.writeString(buf, serializedName); + buf.writeByte(this.action); + ProtocolUtils.writeString(buf, this.objectiveName); + if (this.action != 1) { + ProtocolUtils.writeVarInt(buf, this.value); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return true; + } + + public Component getEntityName() { + return this.entityName; + } + + public int getAction() { + return this.action; + } + + public String getObjectiveName() { + return this.objectiveName; + } + + public int getValue() { + return this.value; + } +} diff --git a/src/main/templates/net/elytrium/limbohub/BuildConstants.java b/src/main/templates/net/elytrium/limbohub/BuildConstants.java new file mode 100644 index 0000000..228e2b6 --- /dev/null +++ b/src/main/templates/net/elytrium/limbohub/BuildConstants.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limbohub; + +// The constants are replaced before compilation +public class BuildConstants { + + public static final String VERSION = "${version}"; +}