Compare commits

..
132 Commits
Author SHA1 Message Date
retrooper b88857386a Release PacketEvents 2.9.0 2025-07-10 00:42:32 +02:00
retrooper 4c48d4c1df Merge pull request #1291 from booky10/feat/publishing-workflow
Automatic release publishing
2025-07-10 00:34:47 +02:00
retrooper 68f8fd3d03 Merge pull request #1290 from booky10/fix/recursive-dialog-resolving
Store dialog click event field as reference for names
2025-07-10 00:29:56 +02:00
retrooper 2d625c6e7a feat: add setters to WrapperPlayServerWorldBorder 2025-07-10 00:15:19 +02:00
booky10 83fe2cce0d Revert "Setup testing setup v2"
This reverts commit 11e4033ec6.
2025-07-09 23:46:20 +02:00
booky10 73bfaebab6 Fix publishing type for github release
Was stable even for snapshots because version isn't set yet in root project
2025-07-09 23:39:57 +02:00
booky10 2ac61fd5cb Fix snapshot detection 2025-07-09 23:30:04 +02:00
booky10 7d31150b35 Add v2.6.0 and v2.7.0 changelogs to CHANGELOG.md 2025-07-09 23:28:19 +02:00
booky10 11e4033ec6 Setup testing setup v2 2025-07-09 23:20:51 +02:00
booky10 a6a8866727 Revert "Setup testing setup"
This reverts commit 84a0759288.
2025-07-09 23:20:08 +02:00
booky10 ce61c3ef52 Fix github tag name 2025-07-09 23:13:50 +02:00
retrooper 4602ea8107 Merge pull request #1251 from booky10/fix/attempt-fixing-random-login-race-condition-injection-issues
Attempt fixing random login race condition injection issues
2025-07-09 23:12:48 +02:00
booky10 5fb1a6c85e Write changelog to github output instead of stdout 2025-07-09 23:12:05 +02:00
retrooper 7e2ac525ac Merge pull request #1287 from booky10/feat/effect-wrapper
Add missing WrapperPlayServerEffect
2025-07-09 23:07:55 +02:00
booky10 7590d6f7de Fix missing loaders field for modrinth publishing 2025-07-09 22:58:24 +02:00
booky10 d7c2284ccb Add gradle preparation step to release workflow 2025-07-09 22:51:30 +02:00
booky10 84a0759288 Setup testing setup 2025-07-09 22:46:10 +02:00
booky10 0ca8e27fc8 Finish implementing release workflow 2025-07-09 22:43:04 +02:00
retrooper 5e30aef0a1 patch: correction of WORLD_BORDER wrapper 2025-07-09 21:26:31 +02:00
retrooper 9e53791fef patch: WrapperPlayServerWorldBorder exception fix for 1.8-1.11.2 2025-07-09 21:12:06 +02:00
booky10 8346918e86 Start implementing release automation workflow 2025-07-09 19:30:31 +02:00
booky10 7714c6253a Store dialog click event field as reference for names 2025-07-09 18:19:35 +02:00
booky10 0a00e970d7 Access ResourceKey location using fields instead of methods
Method order is sort-of unpredictable, my test in 1.21.4/1.21.6 resulted in the registry name being returned instead of the registry entry's name
2025-07-09 17:37:31 +02:00
retrooper d0dd8dc4cd patch: potentially fix SpigotConversionUtil#typeFromBukkitWorld for 1.17+ 2025-07-09 17:18:16 +02:00
booky10andjtJava bc01609828 Add missing WrapperPlayServerEffect
Based on https://github.com/retrooper/packetevents/pull/702

Fixes https://github.com/retrooper/packetevents/issues/415
Fixes https://github.com/retrooper/packetevents/pull/423

Co-authored-by: jtJava <ilovefuud@gmail.com>
2025-07-09 16:59:44 +02:00
booky10 c6e7bcb980 Remove useless zero-byte-suffix for chunk data
Mojang fixed the issue in 1.21.6, but we forgot to remove our fix too
2025-07-09 16:33:27 +02:00
booky10 8987194030 Fix changes to ChatMessage
Some small fixes to https://github.com/retrooper/packetevents/pull/1267

* Uses consistent version-specific gson serializer instead of platform-dependent one
* Uses component getters/setters instead of directly accessing field to allow for overrides to work
2025-07-09 15:52:45 +02:00
booky10 4495db796d Update comment in PlayerManagerImpl 2025-07-09 15:44:29 +02:00
booky10 e1a29a20a7 Small cleanup for listeners 2025-07-09 15:40:57 +02:00
booky10 bd5ef7cb30 Update to support paper's configuration api
Paper no longer creates the player object during the configuration phase, like vanilla
Also, paper has removed the early connection reference in the player object added by CraftBukkit in 1.20.5

PacketEvents now uses the pre-1.20.5 logic for extracting the player reference
As a result, all handshake/status/login/config packets won't have access to the player object;
play packets should always have the player set
2025-07-09 15:38:15 +02:00
booky10 36363c885c Merge branch '2.0' into fix/attempt-fixing-random-login-race-condition-injection-issues 2025-07-09 14:42:12 +02:00
retrooper a198f0e8d6 Merge pull request #1286 from retrooper/revert-1250-dev
Revert "Refactor TaskWrapper into interface and implementation classes"
2025-07-08 19:39:41 +02:00
retrooper 69cbe1e405 Revert "Refactor TaskWrapper into interface and implementation classes" 2025-07-08 19:39:19 +02:00
retrooper 2b0ec87da6 Merge pull request #1250 from ShortyDev/dev
Refactor TaskWrapper into interface and implementation classes
2025-07-08 14:46:55 +02:00
retrooper 08f95b8036 Merge branch '2.0' of https://github.com/retrooper/packetevents into 2.0 2025-07-08 14:34:42 +02:00
retrooper a15ad3a8d7 patch: typo, put actual code in comment; also removed previous sponsor of computational servers; 2025-07-08 14:34:35 +02:00
retrooper fef7f983b0 Merge pull request #1267 from iiAhmedYT/patch-1
Allow getting/setting json in ChatMessage
2025-07-08 14:27:57 +02:00
retrooper 71eae683fb Merge remote-tracking branch 'origin/2.0' into 2.0 2025-07-08 14:21:22 +02:00
retrooper f692f41025 patch: fix the plugin disabling on unsupported version check 2025-07-08 14:21:09 +02:00
retrooper 3b4acde6f8 Merge pull request #1273 from ineanto/2.0
feat: add RemoteChatSession getter
2025-07-08 14:13:17 +02:00
retrooper 6b14595582 patch: import optimization in spigot reflection util 2025-07-08 13:40:58 +02:00
retrooper 50c2a69449 patch: packetevents disables itself when running on an unsupported Minecraft version;
changing "packetevents 2.0 failed to inject" message;
2025-07-07 19:32:23 +02:00
booky 1b5479f04f Merge pull request #1269 from HSGamer/2.0
correctly parse payload in WrapperCommonClientCustomClickAction
2025-06-29 17:20:13 +02:00
booky10 ac0041ff52 Read nbt end tags as null for custom click action packet 2025-06-29 17:13:25 +02:00
ineanto 21226e6ac9 feat: add remote chat session getter via reflection 2025-06-28 21:41:46 +02:00
hsgamer b14f5f5031 correctly parse payload in WrapperCommonClientCustomClickAction
the payload is either an NBT or NBTEnd, but no boolean byte. So we don't have to check optional
close #1268
2025-06-27 13:22:11 +07:00
Ahmed Waleed 6ff9f8c401 Allow getting/setting json in ChatMessage 2025-06-27 02:04:00 +03:00
retrooper 77abb65bab Merge pull request #1264 from booky10/feat/1.21.7-support
1.21.7 support
2025-06-26 08:50:49 +02:00
booky10andSteaf23 001f6b9fd7 Fix InputControl encoding
Co-authored-by: Steaf23 <steafmobile@gmail.com>
2025-06-26 03:44:27 +02:00
booky10 0395378726 Update registries for 1.21.7 2025-06-26 03:09:57 +02:00
booky10 31fe7a89d9 Add 1.21.7 protocol constants 2025-06-26 03:03:38 +02:00
booky d8b47495e4 Merge pull request #1258 from dawon/bugfix/fix-waypoint-info-types
Fix WaypointInfo types
2025-06-20 14:36:29 +02:00
Jakub Zacek cba4603ad8 Fix WaypointInfo types 2025-06-20 13:55:48 +02:00
booky10 f37c74d0d9 Convert PacketEventsEncoder to simple ChannelOutboundHandlerAdapter
This allows cancelling packets without throwing exceptions around

Fixes https://github.com/retrooper/packetevents/issues/1256
2025-06-19 19:34:27 +02:00
booky10 2085dfb84b Skip generating useless CancelPacketException stacktrace 2025-06-19 19:33:40 +02:00
booky10 cdad13d5c5 Fix WrapperPlayClientEntityAction actions for 1.21.6+
Fixes https://github.com/retrooper/packetevents/issues/1254
2025-06-19 19:12:35 +02:00
retrooper 95b50a44e9 Merge pull request #1255 from Cyramek/direction-fix 2025-06-19 17:30:31 +02:00
cyramek 78f630b5c1 fix direction vector being different from vanilla 2025-06-19 16:58:27 +02:00
retrooper 08d000ea82 Merge pull request #1234 from huanmeng-qwq/impl/advancements
Implement WrapperPlayServerUpdateAdvancements
2025-06-19 12:19:42 +02:00
booky10 28c4fdc7ce Merge branch '2.0' into fix/attempt-fixing-random-login-race-condition-injection-issues 2025-06-19 02:58:42 +02:00
booky10 96c81a69ae Fix compatibility with netty v4.0 (e.g. used by minecraft 1.8) 2025-06-19 02:58:21 +02:00
booky10 83720c6a81 Fix race condition on login for versions older than 1.20.5 2025-06-19 02:48:09 +02:00
booky10 c95acffab6 Resolve user in loginevent for 1.20.5+ 2025-06-19 02:01:37 +02:00
booky10 c2beb331f1 Implement support for resolving player connection through transferCookieConnection field
This makes it possible to get the connection info from a player object much sooner than otherwise
2025-06-19 01:57:07 +02:00
booky10 2667431ec9 Don't resolve entity id getter method by name 2025-06-18 23:19:23 +02:00
booky10 21ce0f4d34 Small cleanup to other advancement classes 2025-06-18 23:00:40 +02:00
booky10 21c1a7dfa8 Clean up Advancement class
- Removes version parameter from read method
- Convert comments to javadoc
- Add constructor for constructing Advancement without criteria list parameter
- Reorder getters/setters
2025-06-18 23:00:25 +02:00
booky10 518a995e8e Add abstraction class for criterion value in AdvancementProgress 2025-06-18 22:58:49 +02:00
booky10 e20e0854ab Remove unused ClientAsset 2025-06-18 22:58:28 +02:00
booky10 7c3cde51f0 Reformat WrapperPlayServerUpdateAdvancements
- Convert comments to javadoc
- Reorder getter/setter
- Don't make "showAdvancements" field nullable
2025-06-18 22:33:28 +02:00
booky10 cc18a7ad08 Don't register debug listener 2025-06-18 22:28:49 +02:00
booky10 35fa135e9e Fix formatting of Advancement/AdvancementDisplay 2025-06-18 22:26:52 +02:00
booky10 c68fe76156 Merge branch '2.0' into impl/advancements 2025-06-18 22:23:57 +02:00
retrooper 80846cfea2 Merge pull request #1247 from booky10/feat/1.21.6-support
1.21.6 support
2025-06-18 22:03:28 +02:00
retrooper 1a1b03c1f5 i'm not religious, but god bless aboookyrica 2025-06-18 22:01:26 +02:00
booky10 c411a1925b Also support void promises for sponge 2025-06-18 21:46:08 +02:00
booky10 d8b4f5b1b6 Account for void promises while writing on spigot platform 2025-06-18 21:37:16 +02:00
booky10 486c9a493a Add hydration block state property 2025-06-18 21:02:04 +02:00
booky10 b1ed98f53d Fix fabric connection error 2025-06-18 21:01:53 +02:00
ShortyDev ca8ca782fc refactor TaskWrapper into interface and implementation classes 2025-06-18 20:10:49 +02:00
booky10 c75f039c99 Update adventure to v4.23.0 2025-06-18 19:08:38 +02:00
booky10 c1324fe7fe Update adventure to v4.22.0 2025-06-17 18:06:52 +02:00
booky10 781d4a7c82 Add new ui sound category 2025-06-17 18:06:52 +02:00
retrooper 590479adda patch: ensure consistent naming of #getGameMode and #setGameMode 2025-06-17 16:48:37 +02:00
retrooper df2c50ccfc patch: ensure code can compile 2025-06-17 13:50:49 +02:00
retrooper f5b60ea3f2 feat: changed WrapperPlayServerUpdateAdvancements design; updated it to 1.21.5; relocated advancement classes to advancements folder; (requires some testing still) 2025-06-17 13:45:56 +02:00
booky10 c95194f147 Fix test errors 2025-06-16 05:16:03 +02:00
booky10 56a643f8d0 Implement item attribute display for 1.21.6 2025-06-16 04:54:42 +02:00
booky10 7daed688e0 Update equippable component to 1.21.6 2025-06-16 04:28:11 +02:00
booky10 f3782dffe1 Finish implement dialog bodies 2025-06-16 04:21:09 +02:00
booky10 b06063ebf7 Finish implement dialog inputs 2025-06-16 03:53:28 +02:00
booky10 6cb3d301d8 Finish implementing dialog types 2025-06-16 03:16:39 +02:00
booky10 1aab738dd5 Implement dialog actions 2025-06-16 02:44:07 +02:00
booky10 0aafad8b58 Some more dialog progress 2025-06-13 17:04:21 +02:00
huanmeng-qwq 910d77252e fix: Advancement#read & write 2025-06-13 21:59:52 +08:00
retrooper 524e6ca16d patch: added copyright notice to update advancements wrapper 2025-06-13 15:15:58 +02:00
retrooper 1aa88d05bf feat: more progress to WrapperPlayServerUpdateAdvancements; updating to 1.21.5 (incomplete) 2025-06-13 15:11:40 +02:00
huanmeng-qwq 70271f3694 update 2025-06-13 11:52:41 +08:00
booky10 62201ae3fa Start implementing dialogs 2025-06-11 17:55:36 +02:00
booky10 8ce56fc346 Quick fabric module update 2025-06-11 16:45:44 +02:00
booky10 8029fd1b4e Update block and item tags for 1.21.6 2025-06-11 16:39:27 +02:00
booky10 de19712dbb Add missing WrapperConfigServerPing 2025-06-11 16:33:12 +02:00
booky10 1d41037bdd Implement WrapperPlayClientChangeGameMode 2025-06-11 16:26:53 +02:00
booky10 a98b8c5604 Implement WrapperPlayServerWaypoint 2025-06-11 16:24:47 +02:00
booky10 7d5d2682b7 Implement waypoint api 2025-06-11 16:21:36 +02:00
booky10 2b5f8027f0 Implement new clientbound show dialog packet wrappers partially 2025-06-11 15:45:09 +02:00
booky10 1e37351b48 Implement new clientbound clear dialog packet wrappers 2025-06-11 15:40:49 +02:00
booky10 70c8aad74e Implement new serverbound custom click action packet wrappers 2025-06-11 15:38:03 +02:00
booky10 5527546f05 Update existing packet wrappers to 1.21.6 2025-06-11 15:18:56 +02:00
booky10 56671341ae Update packet type mappings for 1.21.6 2025-06-11 15:03:11 +02:00
booky10 41ea688ff3 Update registries for 1.21.6 2025-06-11 14:54:16 +02:00
booky10 004195144d Implement new cloud height for dimension types 2025-06-11 14:22:55 +02:00
booky10 962aefba17 Add new 1.21.6 version constants 2025-06-11 14:13:15 +02:00
huanmeng-qwq 88ec7a583d remove final modifier 2025-05-28 10:36:09 +08:00
huanmeng-qwq 2303d362d7 add setter 2025-05-28 10:32:34 +08:00
retrooper da0e95fa03 Merge pull request #1238 from booky10/fix/enchantment-refs
Fix issues with enchantment loading
2025-05-25 11:56:07 +02:00
booky10 c98bfad8b8 Merge branch '2.0' into fix/enchantment-refs 2025-05-25 04:59:33 +02:00
booky10 3dfb8389dc Bump gradle wrapper validation action to v4
I hope this fixes these random errors while building
2025-05-25 04:57:03 +02:00
booky10 7c74dd2e05 Allocate new buffer when re-encoding clientbound buffers with a limited capacity
Bungee now limits the maximum capacity of bytebufs, presumably because of https://github.com/SpigotMC/BungeeCord/commit/1279cca971b2d32b6bd75103d3a555fbbd629392

This now allocates a completely new bytebuf to ensure we don't run into the maximum bytebuf capacity

Fixes https://github.com/retrooper/packetevents/issues/1227
2025-05-25 04:56:21 +02:00
booky10 05eabbf667 Decode enchantment type exclusive set as mapped entity ref set on registry synchronization 2025-05-25 04:22:12 +02:00
booky10 d1cafe2bcc Resolve self-reference registry after adding entries to registry
Only relevant to enchantments for now
2025-05-25 03:57:49 +02:00
booky10 26078fcb59 Implement MappedEntityRefSet 2025-05-25 03:44:03 +02:00
booky10 3d602b9f96 Fix item reading/writing for 1.13/1.13.1
Just for future reference, so I don't have to remap + decompile these jars again:
* 1.12.2 logic: https://haste.pinofett.de/degt0ejhbr.java
* 1.13/1.13.1 logic: https://haste.pinofett.de/omr31l9mv9.java
* 1.13.2 logic: https://haste.pinofett.de/iul6y7vvny.java
2025-05-25 02:57:11 +02:00
huanmeng-qwq df4f9e425e update 2025-05-24 14:44:41 +08:00
huanmeng-qwq efb9766388 convert time to Optional 2025-05-23 18:17:27 +08:00
huanmeng-qwq ab0ad68d43 update 2025-05-23 18:12:38 +08:00
huanmeng-qwq 1be8db5ff4 update 2025-05-23 18:09:36 +08:00
huanmeng-qwq 1f68ee1f46 convert showAdvances to Optional 2025-05-23 18:00:13 +08:00
huanmeng-qwq e7c6ae4726 Implement WrapperPlayServerStatistics 2025-05-23 17:48:09 +08:00
huanmeng-qwq 5db1671895 Implement WrapperPlayServerUpdateAdvancements 2025-05-23 17:47:49 +08:00
retrooper 023a0da619 packetevents description change 2025-05-16 15:46:40 +02:00
retrooper 51863fa435 bump version to 2.8.1 snapshot 2025-05-15 15:20:19 +02:00
215 changed files with 217587 additions and 520 deletions

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- name: Validate gradle wrapper
uses: gradle/actions/wrapper-validation@v3
uses: gradle/actions/wrapper-validation@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
+51
View File
@@ -0,0 +1,51 @@
name: Release
on:
workflow_dispatch:
permissions:
contents: write
jobs:
release:
name: Publish Release
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Validate gradle wrapper
uses: gradle/actions/wrapper-validation@v4
- name: Setup java 21
uses: actions/setup-java@v4
with:
java-version: 21
distribution: temurin
- name: Prepare gradle
run: ./gradlew printVersion --no-daemon
- name: Extract changelog
id: changelog
run: |
OUTPUT_DELIMITER="$(uuidgen)"
echo "changelog<<$OUTPUT_DELIMITER" >> "$GITHUB_OUTPUT"
# extract tag name and use it to get changelog section
TAG_NAME="v$(./gradlew -q --no-daemon printVersion)"
awk -v tag="${TAG_NAME}" '
$1 == "!!!!!" && $2 == tag {found=1; next}
$1 == "!!!!!" && found {exit}
found
' CHANGELOG.md >> "$GITHUB_OUTPUT"
# end output
echo "$OUTPUT_DELIMITER" >> "$GITHUB_OUTPUT"
- name: Publish plugin
env:
GITHUB_API_TOKEN: ${{ github.token }}
MODRINTH_API_TOKEN: ${{ secrets.MODRINTH_PUBLISH_TOKEN }}
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
run: ./gradlew publishMods --no-daemon -PnoDryPublish
+151
View File
@@ -0,0 +1,151 @@
!!!!! v2.9.0
# PacketEvents 2.9.0 is out now! 🎉
### New update now supports Minecraft version 1.21.6 & 1.21.7. We suggest that you update, as numerous bugs have been fixed.
# Brief Announcement 📢⚠️
### We have our very own website: https://packetevents.com
### Documentation: https://docs.packetevents.com
### JavaDocs: https://javadocs.packetevents.com
## What's Changed? (Summary)
* Added 1.21.6 support
* Added 1.21.7 support
* Bug fixes
**View Full Changelog**: https://github.com/retrooper/packetevents/compare/v2.8.0...v2.9.0
## If you want to support PacketEvents, consider sponsoring us on [GitHub Sponsors](https://github.com/sponsors/retrooper)
## Maven/Gradle Dependency
### **Check out**: https://docs.packetevents.com/getting-started
!!!!! v2.8.0
# PacketEvents 2.8.0 is out now! 🎉
### New update now supports Minecraft version 1.21.5. Please update, as numerous bugs have been fixed.
# Brief Announcement 📢⚠️
### We have our very own website: https://packetevents.com
### Documentation: https://docs.packetevents.com
### JavaDocs: https://javadocs.packetevents.com
## What's Changed? (Summary)
* Added 1.21.5 support
* Various bugs fixed
**View Full Changelog**: https://github.com/retrooper/packetevents/compare/v2.7.0...v2.8.0
## Contributors 🏅
This update wouldn't be possible without these contributors ❤️: @booky10, @Axionize, @Bram1903, @ManInMyVan, @ytnoos,
@AoElite, @HaHaWTH, @divinepablo, @scienziatopazzo, @bridgelol, @MachineBreaker, @Luuzzi, @Beaness, @Rubenicos, @Loyisa
## New Contributors
* @HaHaWTH made their first contribution in https://github.com/retrooper/packetevents/pull/1097
* @divinepablo made their first contribution in https://github.com/retrooper/packetevents/pull/1098
* @scienziatopazzo made their first contribution in https://github.com/retrooper/packetevents/pull/1105
* @bridgelol made their first contribution in https://github.com/retrooper/packetevents/pull/1199
* @Luuzzi made their first contribution in https://github.com/retrooper/packetevents/pull/1197
* @Beaness made their first contribution in https://github.com/retrooper/packetevents/pull/1212
* @Rubenicos made their first contribution in https://github.com/retrooper/packetevents/pull/1162
* @Loyisa made their first contribution in https://github.com/retrooper/packetevents/pull/1220
## If you want to support PacketEvents, consider sponsoring us on [GitHub Sponsors](https://github.com/sponsors/retrooper)
## Maven/Gradle Dependency
### **Check out**: https://docs.packetevents.com/getting-started
!!!!! v2.7.0
# PacketEvents v2.7.0 is out now! 🎉
### New update with support of the latest Minecraft version. In addition to that, countless bugs were fixed.
# Brief Announcement 📢⚠️
### We have our own website: https://packetevents.com
### Documentation: https://docs.packetevents.com
### JavaDocs: https://javadocs.packetevents.com
## What's Changed? (Summary)
* Added 1.21.4 support
* Fix: Potion contents item component for 1.21.2/1.21.3
* Fix: NPC objects to have unique server team names
* Add missing ServerVersion#V_1_9_1
* Validation checks added for window click type
**Full Changelog**: https://github.com/retrooper/packetevents/compare/v2.6.0...v2.7.0
## New Contributors
* @Axionize made their first contribution in https://github.com/retrooper/packetevents/pull/1048
* @ShreyasAyyengar made their first contribution in https://github.com/retrooper/packetevents/pull/1044
* @mfnalex made their first contribution in https://github.com/retrooper/packetevents/pull/1076
## Contributors 🏅
This update wouldn't be possible without these contributors ❤️: @booky10, @rafi67000, @ShreyasAyyengar, @AoElite,
@Tecnio, @Axionize, @mfnalex
### If you want to support PacketEvents, and any upcoming events we plan for the future, consider sponsoring us on [GitHub Sponsors](https://github.com/sponsors/retrooper)
## Maven/Gradle Dependency
### **Check out**: https://docs.packetevents.com/getting-started
!!!!! v2.6.0
# PacketEvents v2.6.0 is out now! 🎉
### New update with support of new platforms & the latest Minecraft version. In addition to that, countless bugs were fixed.
# Brief Announcement 📢⚠️
### We have our own website: https://packetevents.com
### Documentation: https://docs.packetevents.com
### JavaDocs: https://javadocs.packetevents.com
## What's Changed? (Summary)
* 1.21.2 and 1.21.3 Minecraft support
* Sponge support added
* Fabric support added (also server-side)
* Utilizing the proper bStats API
* Registry element issues fixed
* Mappings issues fixed
* AdvancedSlimePaper issues fixed
* BungeeCord issues fixed
### **Full Changelog**: https://github.com/retrooper/packetevents/compare/v2.5.0...v2.6.0
## Contributors 🏅
This update wouldn't be possible without these contributors ❤️: @booky10, @ManInMyVan, @SamB440, @rafi67000, @Bram1903,
@AbhigyaKrishna
### If you want to support PacketEvents, and any upcoming events we plan for the future, consider sponsoring us on [GitHub Sponsors](https://github.com/sponsors/retrooper)
## Maven/Gradle Dependency
### **Check out**: https://docs.packetevents.com/getting-started
!!!!!
+1 -3
View File
@@ -14,8 +14,8 @@
- [Getting Started](https://docs.packetevents.com/getting-started)
- [JavaDocs](https://javadocs.packetevents.com)
- [Releases](https://github.com/retrooper/packetevents/releases/)
- [GitHub](https://github.com/retrooper/packetevents/releases/)
- [Modrinth](https://modrinth.com/plugin/packetevents)
- [GitHub](https://github.com/retrooper/packetevents/releases/)
- [SpigotMC](https://www.spigotmc.org/resources/packetevents-api.80279/)
- [Development Builds](https://ci.codemc.io/job/retrooper/job/packetevents)
- [Statistics](https://bstats.org/plugin/bukkit/packetevents/11327)
@@ -28,8 +28,6 @@
<a href="https://www.ej-technologies.com"><img src="https://www.ej-technologies.com/images/product_banners/jprofiler_small.png"><br>
<a href="https://github.com/radstevee"><img src="https://github.com/radstevee.png" width="50px" alt="radstevee"/></a><br>
<h3>Credits</h3>
<i>Here are some projects that we are heavily inspired by, thus we have integrated</i>\
@@ -32,6 +32,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -44,7 +45,7 @@ public abstract class ProtocolPacketEvent extends PacketEvent implements PlayerE
private final Object channel;
private final ConnectionState connectionState;
private final User user;
private final Object player;
private Object player;
private Object byteBuf;
private final int packetID;
private final PacketTypeCommon packetType;
@@ -145,6 +146,12 @@ public abstract class ProtocolPacketEvent extends PacketEvent implements PlayerE
return (T) player;
}
@Deprecated
@ApiStatus.Internal
public void setPlayer(Object player) {
this.player = player;
}
/**
* Gets the connection state this packet has been sent/received in.
*/
@@ -2,4 +2,9 @@ package com.github.retrooper.packetevents.exception;
public class CancelPacketException extends RuntimeException {
public static final CancelPacketException INSTANCE = new CancelPacketException();
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
@@ -37,7 +37,9 @@ import com.github.retrooper.packetevents.wrapper.handshaking.client.WrapperHands
import com.github.retrooper.packetevents.wrapper.login.server.WrapperLoginServerLoginSuccess;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerJoinGame;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerRespawn;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public class InternalPacketListener extends PacketListenerAbstract {
public InternalPacketListener() {
@@ -68,7 +70,9 @@ public class InternalPacketListener extends PacketListenerAbstract {
PacketEvents.getAPI().getProtocolManager().setChannel(profile.getUUID(), channel);
}
PacketEvents.getAPI().getLogManager().debug("Mapped player UUID with their channel.");
if (PacketEvents.getAPI().getLogManager().isDebug()) {
PacketEvents.getAPI().getLogManager().debug("Mapped player UUID with their channel " + profile.getUUID() + " " + channel);
}
// Switch the user's connection state to new state, but the variable event.getConnectionState() remains LOGIN
// We switch user state immediately to remain in sync with vanilla, allowing you to encode packets immediately
@@ -86,12 +90,11 @@ public class InternalPacketListener extends PacketListenerAbstract {
WrapperConfigServerRegistryData packet = new WrapperConfigServerRegistryData(event);
if (packet.getElements() != null) { // 1.20.2 to 1.20.5
SynchronizedRegistriesHandler.handleRegistry(user, packet.getServerVersion().toClientVersion(),
SynchronizedRegistriesHandler.handleRegistry(user, packet,
packet.getRegistryKey(), packet.getElements());
}
if (packet.getRegistryData() != null) { // since 1.20.5
SynchronizedRegistriesHandler.handleLegacyRegistries(user, packet.getServerVersion()
.toClientVersion(), packet.getRegistryData());
SynchronizedRegistriesHandler.handleLegacyRegistries(user, packet, packet.getRegistryData());
}
}
@@ -101,7 +104,7 @@ public class InternalPacketListener extends PacketListenerAbstract {
user.setEntityId(joinGame.getEntityId());
if (joinGame.getDimensionCodec() != null) { // 1.16 to 1.20.1
SynchronizedRegistriesHandler.handleLegacyRegistries(user, joinGame.getServerVersion().toClientVersion(),
SynchronizedRegistriesHandler.handleLegacyRegistries(user, joinGame,
joinGame.getDimensionCodec());
}
@@ -50,7 +50,7 @@ public enum ServerVersion {
//1.20 and 1.20.1 have the same protocol version. 1.20.3 and 1.20.4 have the same protocol version. 1.20.5 and 1.20.6 have the same protocol version
V_1_20(763), V_1_20_1(763), V_1_20_2(764), V_1_20_3(765), V_1_20_4(765), V_1_20_5(766), V_1_20_6(766),
//1.21 and 1.21.1 have the same protocol version. 1.21.2 and 1.21.3 have the same protocol version
V_1_21(767), V_1_21_1(767), V_1_21_2(768), V_1_21_3(768), V_1_21_4(769), V_1_21_5(770),
V_1_21(767), V_1_21_1(767), V_1_21_2(768), V_1_21_3(768), V_1_21_4(769), V_1_21_5(770), V_1_21_6(771), V_1_21_7(772),
//TODO UPDATE Add server version constant
ERROR(-1, true);
@@ -0,0 +1,144 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.advancements;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
public class Advancement {
private @Nullable ResourceLocation parent;
private @Nullable AdvancementDisplay display;
/**
* Removed with 1.20.2
*/
private List<String> criteria;
private List<List<String>> requirements;
/**
* Added with 1.20
*/
private boolean sendsTelemetryData;
public Advancement(
@Nullable ResourceLocation parent,
@Nullable AdvancementDisplay display,
List<List<String>> requirements,
boolean sendsTelemetryData
) {
this(parent, display, Collections.emptyList(), requirements, sendsTelemetryData);
}
@ApiStatus.Obsolete
public Advancement(
@Nullable ResourceLocation parent,
@Nullable AdvancementDisplay display,
List<String> criteria,
List<List<String>> requirements,
boolean sendsTelemetryData
) {
this.parent = parent;
this.display = display;
this.criteria = criteria;
this.requirements = requirements;
this.sendsTelemetryData = sendsTelemetryData;
}
public static Advancement read(PacketWrapper<?> wrapper) {
ResourceLocation parentId = wrapper.readOptional(ResourceLocation::read);
AdvancementDisplay display = wrapper.readOptional(AdvancementDisplay::read);
List<String> criteria = wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_20_2)
? wrapper.readList(PacketWrapper::readString) : null;
List<List<String>> requirements = wrapper.readList(
ew -> wrapper.readList(PacketWrapper::readString));
boolean sendsTelemetryData = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20)
&& wrapper.readBoolean();
return new Advancement(parentId, display, criteria, requirements, sendsTelemetryData);
}
public static void write(PacketWrapper<?> wrapper, Advancement advancement) {
wrapper.writeOptional(advancement.parent, ResourceLocation::write);
wrapper.writeOptional(advancement.display, AdvancementDisplay::write);
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_20_2)) {
wrapper.writeList(advancement.criteria, PacketWrapper::writeString);
}
wrapper.writeList(advancement.getRequirements(), (ew, anyList) ->
ew.writeList(anyList, PacketWrapper::writeString));
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20)) {
wrapper.writeBoolean(advancement.sendsTelemetryData);
}
}
public @Nullable ResourceLocation getParent() {
return this.parent;
}
public void setParent(@Nullable ResourceLocation parent) {
this.parent = parent;
}
public @Nullable AdvancementDisplay getDisplay() {
return this.display;
}
public void setDisplay(@Nullable AdvancementDisplay display) {
this.display = display;
}
/**
* Removed with 1.20.2
*/
public List<String> getCriteria() {
return this.criteria;
}
/**
* Removed with 1.20.2
*/
public void setCriteria(List<String> criteria) {
this.criteria = criteria;
}
public List<List<String>> getRequirements() {
return this.requirements;
}
public void setRequirements(List<List<String>> requirements) {
this.requirements = requirements;
}
/**
* Added with 1.20
*/
public boolean isSendsTelemetryData() {
return this.sendsTelemetryData;
}
/**
* Added with 1.20
*/
public void setSendsTelemetryData(boolean sendsTelemetryData) {
this.sendsTelemetryData = sendsTelemetryData;
}
}
@@ -0,0 +1,168 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.advancements;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
public final class AdvancementDisplay {
public static final int FLAG_HAS_BACKGROUND = 0b001;
public static final int FLAG_SHOW_TOAST = 0b010;
public static final int FLAG_HIDDEN = 0b100;
private Component title;
private Component description;
private ItemStack icon;
private AdvancementType type;
private boolean showToast;
private boolean hidden;
private @Nullable ResourceLocation background;
private float x;
private float y;
public AdvancementDisplay(Component title, Component description, ItemStack icon, AdvancementType type, @Nullable ResourceLocation background, boolean showToast,
boolean hidden, float x, float y) {
this.title = title;
this.description = description;
this.icon = icon;
this.type = type;
this.showToast = showToast;
this.hidden = hidden;
this.background = background;
this.x = x;
this.y = y;
}
public static AdvancementDisplay read(PacketWrapper<?> wrapper) {
Component title = wrapper.readComponent();
Component description = wrapper.readComponent();
ItemStack icon = wrapper.readItemStack();
AdvancementType type = wrapper.readEnum(AdvancementType.class);
int flags = wrapper.readInt();
ResourceLocation background = (flags & FLAG_HAS_BACKGROUND) != 0 ? ResourceLocation.read(wrapper) : null;
boolean showToast = (flags & FLAG_SHOW_TOAST) != 0;
boolean hidden = (flags & FLAG_HIDDEN) != 0;
float x = wrapper.readFloat();
float y = wrapper.readFloat();
return new AdvancementDisplay(title, description, icon, type, background, showToast, hidden, x, y);
}
public static void write(PacketWrapper<?> wrapper, AdvancementDisplay display) {
wrapper.writeComponent(display.title);
wrapper.writeComponent(display.description);
wrapper.writeItemStack(display.icon);
wrapper.writeEnum(display.type);
wrapper.writeInt(display.packFlags());
if (display.background != null) {
ResourceLocation.write(wrapper, display.background);
}
wrapper.writeFloat(display.x);
wrapper.writeFloat(display.y);
}
public int packFlags() {
int flags = 0;
if (this.background != null) {
flags |= FLAG_HAS_BACKGROUND;
}
if (this.showToast) {
flags |= FLAG_SHOW_TOAST;
}
if (this.hidden) {
flags |= FLAG_HIDDEN;
}
return flags;
}
public Component getTitle() {
return this.title;
}
public void setTitle(Component title) {
this.title = title;
}
public Component getDescription() {
return this.description;
}
public void setDescription(Component description) {
this.description = description;
}
public ItemStack getIcon() {
return this.icon;
}
public void setIcon(ItemStack icon) {
this.icon = icon;
}
public AdvancementType getType() {
return this.type;
}
public void setType(AdvancementType type) {
this.type = type;
}
public boolean isShowToast() {
return this.showToast;
}
public void setShowToast(boolean showToast) {
this.showToast = showToast;
}
public boolean isHidden() {
return this.hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public @Nullable ResourceLocation getBackground() {
return this.background;
}
public void setBackground(@Nullable ResourceLocation background) {
this.background = background;
}
public float getX() {
return this.x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return this.y;
}
public void setY(float y) {
this.y = y;
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.advancements;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
public final class AdvancementHolder {
private ResourceLocation identifier;
private Advancement advancement;
public AdvancementHolder(ResourceLocation identifier, Advancement advancement) {
this.identifier = identifier;
this.advancement = advancement;
}
public static AdvancementHolder read(PacketWrapper<?> wrapper) {
ResourceLocation identifier = wrapper.readIdentifier();
Advancement advancement = Advancement.read(wrapper);
return new AdvancementHolder(identifier, advancement);
}
public static void write(PacketWrapper<?> wrapper, AdvancementHolder holder) {
wrapper.writeIdentifier(holder.identifier);
Advancement.write(wrapper, holder.advancement);
}
public ResourceLocation getIdentifier() {
return this.identifier;
}
public void setIdentifier(ResourceLocation identifier) {
this.identifier = identifier;
}
public Advancement getAdvancement() {
return this.advancement;
}
public void setAdvancement(Advancement advancement) {
this.advancement = advancement;
}
}
@@ -0,0 +1,75 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.advancements;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.Nullable;
import java.util.Map;
public final class AdvancementProgress {
private Map<String, CriterionProgress> criteria;
public AdvancementProgress(Map<String, CriterionProgress> criteria) {
this.criteria = criteria;
}
public static AdvancementProgress read(PacketWrapper<?> wrapper) {
Map<String, CriterionProgress> criteria = wrapper.readMap(PacketWrapper::readString, CriterionProgress::read);
return new AdvancementProgress(criteria);
}
public static void write(PacketWrapper<?> wrapper, AdvancementProgress progress) {
wrapper.writeMap(progress.getCriteria(), PacketWrapper::writeString, CriterionProgress::write);
}
public Map<String, CriterionProgress> getCriteria() {
return this.criteria;
}
public void setCriteria(Map<String, CriterionProgress> criteria) {
this.criteria = criteria;
}
public static final class CriterionProgress {
private @Nullable Long obtainedTimestamp;
public CriterionProgress(@Nullable Long obtainedTimestamp) {
this.obtainedTimestamp = obtainedTimestamp;
}
public static CriterionProgress read(PacketWrapper<?> wrapper) {
return new CriterionProgress(wrapper.readOptional(PacketWrapper::readLong));
}
public static void write(PacketWrapper<?> wrapper, CriterionProgress progress) {
wrapper.writeOptional(progress.obtainedTimestamp, PacketWrapper::writeLong);
}
public @Nullable Long getObtainedTimestamp() {
return this.obtainedTimestamp;
}
public void setObtainedTimestamp(@Nullable Long obtainedTimestamp) {
this.obtainedTimestamp = obtainedTimestamp;
}
}
}
@@ -0,0 +1,27 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.advancements;
public enum AdvancementType {
TASK,
CHALLENGE,
GOAL,
;
}
@@ -0,0 +1,39 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface AttributeDisplay {
static AttributeDisplay read(PacketWrapper<?> wrapper) {
AttributeDisplayType<?> type = wrapper.readMappedEntity(AttributeDisplayTypes.getRegistry());
return type.read(wrapper);
}
@SuppressWarnings("unchecked") // not unchecked
static void write(PacketWrapper<?> wrapper, AttributeDisplay display) {
wrapper.writeMappedEntity(display.getType());
((AttributeDisplayType<? super AttributeDisplay>) display.getType()).write(wrapper, display);
}
AttributeDisplayType<?> getType();
}
@@ -0,0 +1,31 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface AttributeDisplayType<T extends AttributeDisplay> extends MappedEntity {
T read(PacketWrapper<?> wrapper);
void write(PacketWrapper<?> wrapper, T display);
}
@@ -0,0 +1,56 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class AttributeDisplayTypes {
private static final VersionedRegistry<AttributeDisplayType<?>> REGISTRY = new VersionedRegistry<>("attribute_display_type");
private AttributeDisplayTypes() {
}
@ApiStatus.Internal
public static <T extends AttributeDisplay> AttributeDisplayType<T> define(
String name, PacketWrapper.Reader<T> reader, PacketWrapper.Writer<T> writer
) {
return REGISTRY.define(name, data ->
new StaticAttributeDisplayType<>(data, reader, writer));
}
public static VersionedRegistry<AttributeDisplayType<?>> getRegistry() {
return REGISTRY;
}
public static final AttributeDisplayType<DefaultAttributeDisplay> DEFAULT = define("default",
DefaultAttributeDisplay::read, DefaultAttributeDisplay::write);
public static final AttributeDisplayType<HiddenAttributeDisplay> HIDDEN = define("hidden",
HiddenAttributeDisplay::read, HiddenAttributeDisplay::write);
public static final AttributeDisplayType<OverrideAttributeDisplay> OVERRIDE = define("override",
OverrideAttributeDisplay::read, OverrideAttributeDisplay::write);
static {
REGISTRY.unloadMappings();
}
}
@@ -187,6 +187,22 @@ public final class Attributes {
public static final Attribute TEMPT_RANGE = define("tempt_range",
null, 10d, 0d, 2048d);
/**
* Added with 1.21.6
*/
public static final Attribute CAMERA_DISTANCE = define("camera_distance",
null, 4d, 0d, 32d);
/**
* Added with 1.21.6
*/
public static final Attribute WAYPOINT_TRANSMIT_RANGE = define("waypoint_transmit_range",
null, 0d, 0d, 60_000_000d);
/**
* Added with 1.21.6
*/
public static final Attribute WAYPOINT_RECEIVE_RANGE = define("waypoint_receive_range",
null, 0d, 0d, 60_000_000d);
/**
* This attribute has been renamed in 1.20.5 to {@link #JUMP_STRENGTH}
*/
@@ -0,0 +1,44 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class DefaultAttributeDisplay implements AttributeDisplay {
public static final DefaultAttributeDisplay INSTANCE = new DefaultAttributeDisplay();
private DefaultAttributeDisplay() {
}
public static DefaultAttributeDisplay read(PacketWrapper<?> wrapper) {
return INSTANCE;
}
public static void write(PacketWrapper<?> wrapper, DefaultAttributeDisplay display) {
// NO-OP
}
@Override
public AttributeDisplayType<?> getType() {
return AttributeDisplayTypes.DEFAULT;
}
}
@@ -0,0 +1,44 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class HiddenAttributeDisplay implements AttributeDisplay {
public static final HiddenAttributeDisplay INSTANCE = new HiddenAttributeDisplay();
private HiddenAttributeDisplay() {
}
public static HiddenAttributeDisplay read(PacketWrapper<?> wrapper) {
return INSTANCE;
}
public static void write(PacketWrapper<?> wrapper, HiddenAttributeDisplay display) {
// NO-OP
}
@Override
public AttributeDisplayType<?> getType() {
return AttributeDisplayTypes.HIDDEN;
}
}
@@ -0,0 +1,64 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
@NullMarked
public class OverrideAttributeDisplay implements AttributeDisplay {
private final Component component;
public OverrideAttributeDisplay(Component component) {
this.component = component;
}
public static OverrideAttributeDisplay read(PacketWrapper<?> wrapper) {
return new OverrideAttributeDisplay(wrapper.readComponent());
}
public static void write(PacketWrapper<?> wrapper, OverrideAttributeDisplay display) {
wrapper.writeComponent(display.component);
}
@Override
public AttributeDisplayType<?> getType() {
return AttributeDisplayTypes.OVERRIDE;
}
public Component getComponent() {
return this.component;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof OverrideAttributeDisplay)) return false;
OverrideAttributeDisplay that = (OverrideAttributeDisplay) obj;
return this.component.equals(that.component);
}
@Override
public int hashCode() {
return Objects.hashCode(this.component);
}
}
@@ -0,0 +1,52 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.attribute;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.PacketWrapper.Reader;
import com.github.retrooper.packetevents.wrapper.PacketWrapper.Writer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticAttributeDisplayType<T extends AttributeDisplay> extends AbstractMappedEntity implements AttributeDisplayType<T> {
private final Reader<T> reader;
private final Writer<T> writer;
@ApiStatus.Internal
public StaticAttributeDisplayType(@Nullable TypesBuilderData data, Reader<T> reader, Writer<T> writer) {
super(data);
this.reader = reader;
this.writer = writer;
}
@Override
public T read(PacketWrapper<?> wrapper) {
return this.reader.apply(wrapper);
}
@Override
public void write(PacketWrapper<?> wrapper, T display) {
this.writer.accept(wrapper, display);
}
}
@@ -72,7 +72,12 @@ public class ChatTypeDecoration {
wrapper.writeStyle(decoration.style);
}
@Deprecated
public static ChatTypeDecoration decode(NBT nbt, ClientVersion version) {
return decode(nbt, PacketWrapper.createDummyWrapper(version));
}
public static ChatTypeDecoration decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
String translationKey = compound.getStringTagValueOrThrow("translation_key");
NBT paramsTag = compound.getTagOrThrow("parameters");
@@ -90,11 +95,16 @@ public class ChatTypeDecoration {
}
NBTCompound styleTag = compound.getCompoundTagOrNull("style");
Style style = styleTag == null ? empty() :
AdventureSerializer.serializer(version).nbt().deserializeStyle(styleTag);
wrapper.getSerializers().nbt().deserializeStyle(styleTag, wrapper);
return new ChatTypeDecoration(translationKey, params, style);
}
@Deprecated
public static NBT encode(ChatTypeDecoration decoration, ClientVersion version) {
return encode(decoration, PacketWrapper.createDummyWrapper(version));
}
public static NBT encode(ChatTypeDecoration decoration, PacketWrapper<?> wrapper) {
NBTList<NBTString> paramsTag = NBTList.createStringList();
for (Parameter param : decoration.parameters) {
paramsTag.addTag(new NBTString(param.getId()));
@@ -104,8 +114,8 @@ public class ChatTypeDecoration {
compound.setTag("translation_key", new NBTString(decoration.translationKey));
compound.setTag("parameters", paramsTag);
if (!decoration.style.isEmpty()) {
compound.setTag("style", AdventureSerializer.serializer(version)
.nbt().serializeStyle(decoration.style));
compound.setTag("style", wrapper.getSerializers()
.nbt().serializeStyle(decoration.style, wrapper));
}
return compound;
}
@@ -28,6 +28,20 @@ import java.util.Optional;
public class Node {
public static final byte TYPE_MASK = 0b11;
public static final byte TYPE_ROOT = 0b00;
public static final byte TYPE_LITERAL = 0b01;
public static final byte TYPE_ARGUMENT = 0b10;
public static final byte FLAG_MASK = ~TYPE_MASK;
public static final byte FLAG_EXECUTABLE = 0b000100;
public static final byte FLAG_REDIRECT = 0b001000;
public static final byte FLAG_CUSTOM_SUGGESTIONS = 0b010000;
/**
* Added with 1.21.6
*/
public static final byte FLAG_RESTRICTED = 0b100000;
private byte flags;
private List<Integer> children;
private int redirectNodeIndex;
@@ -27,6 +27,7 @@ import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.ArrayList;
import java.util.Arrays;
@@ -36,9 +37,10 @@ import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
@NullMarked
public final class Parsers {
private static final VersionedRegistry<Parser> REGISTRY = new VersionedRegistry<>("argument_type");
private static final VersionedRegistry<Parser> REGISTRY = new VersionedRegistry<>("command_argument_type");
private Parsers() {
}
@@ -218,6 +220,15 @@ public final class Parsers {
(wrapper, value) -> wrapper.writeIdentifier((ResourceLocation) value.get(0))
);
/**
* Added with 1.21.6
*/
public static final Parser HEX_COLOR = define("hex_color", null, null);
/**
* Added with 1.21.6
*/
public static final Parser DIALOG = define("dialog", null, null);
static {
REGISTRY.unloadMappings();
}
@@ -0,0 +1,71 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ChangePageClickEvent implements ClickEvent {
private final int page;
@ApiStatus.Obsolete
public ChangePageClickEvent(String page) {
this(Integer.parseInt(page));
}
public ChangePageClickEvent(int page) {
this.page = page;
}
public static ChangePageClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
int page = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5)
? compound.getNumberTagValueOrThrow("page").intValue()
: Integer.parseInt(compound.getStringTagValueOrThrow("value"));
return new ChangePageClickEvent(page);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, ChangePageClickEvent clickEvent) {
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5)) {
compound.setTag("page", new NBTInt(clickEvent.page));
} else {
compound.setTag("value", new NBTString(Integer.toString(clickEvent.page)));
}
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.CHANGE_PAGE;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.changePage(this.page);
}
public int getPage() {
return this.page;
}
}
@@ -0,0 +1,76 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.dialog.Dialog;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTEnd;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.adventure.NbtTagHolder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.event.ClickEvent.Payload;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface ClickEvent {
static ClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
String actionName = compound.getStringTagValueOrThrow("action");
ClickEventAction<?> action = ClickEventActions.getRegistry().getByNameOrThrow(actionName);
return action.decode(compound, wrapper);
}
@SuppressWarnings("unchecked") // not unchecked
static void encode(NBTCompound compound, PacketWrapper<?> wrapper, ClickEvent clickEvent) {
compound.set("action", clickEvent.getAction().getName(), ResourceLocation::encode, wrapper);
((ClickEventAction<? super ClickEvent>) clickEvent.getAction()).encode(compound, wrapper, clickEvent);
}
ClickEventAction<?> getAction();
static ClickEvent fromAdventure(net.kyori.adventure.text.event.ClickEvent clickEvent) {
switch (clickEvent.action()) {
case OPEN_URL:
return new OpenUrlClickEvent(clickEvent.value());
case OPEN_FILE:
return new OpenFileClickEvent(clickEvent.value());
case RUN_COMMAND:
return new RunCommandClickEvent(clickEvent.value());
case SUGGEST_COMMAND:
return new SuggestCommandClickEvent(clickEvent.value());
case CHANGE_PAGE:
return new ChangePageClickEvent(clickEvent.value());
case COPY_TO_CLIPBOARD:
return new CopyToClipboardClickEvent(clickEvent.value());
case SHOW_DIALOG:
return new ShowDialogClickEvent((Dialog) ((Payload.Dialog) clickEvent.payload()).dialog());
case CUSTOM:
Payload.Custom payload = (Payload.Custom) clickEvent.payload();
NbtTagHolder nbtTag = (NbtTagHolder) payload.nbt();
return new CustomClickEvent(
new ResourceLocation(payload.key()),
nbtTag.getTag() instanceof NBTEnd ? null : nbtTag.getTag()
);
default:
throw new UnsupportedOperationException("Unsupported clickevent: " + clickEvent);
}
}
net.kyori.adventure.text.event.ClickEvent asAdventure();
}
@@ -0,0 +1,34 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface ClickEventAction<T extends ClickEvent> extends MappedEntity {
boolean isAllowFromServer();
T decode(NBTCompound compound, PacketWrapper<?> wrapper);
void encode(NBTCompound compound, PacketWrapper<?> wrapper, T clickEvent);
}
@@ -0,0 +1,89 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class ClickEventActions {
private static final VersionedRegistry<ClickEventAction<?>> REGISTRY = new VersionedRegistry<>("click_event_action");
private ClickEventActions() {
}
@ApiStatus.Internal
public static <T extends ClickEvent> ClickEventAction<T> define(
String name, boolean allowFromServer,
NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder
) {
return REGISTRY.define(name, data -> new StaticClickEventAction<>(
data, allowFromServer, decoder, encoder));
}
public static VersionedRegistry<ClickEventAction<?>> getRegistry() {
return REGISTRY;
}
public static final ClickEventAction<OpenUrlClickEvent> OPEN_URL = define("open_url", true,
OpenUrlClickEvent::decode, OpenUrlClickEvent::encode);
public static final ClickEventAction<OpenFileClickEvent> OPEN_FILE = define("open_file", false,
OpenFileClickEvent::decode, OpenFileClickEvent::encode);
public static final ClickEventAction<RunCommandClickEvent> RUN_COMMAND = define("run_command", true,
RunCommandClickEvent::decode, RunCommandClickEvent::encode);
/**
* Removed in 1.9
*/
@ApiStatus.Obsolete
public static final ClickEventAction<TwitchUserInfoClickEvent> TWITCH_USER_INFO = define("twitch_user_info", false,
TwitchUserInfoClickEvent::decode, TwitchUserInfoClickEvent::encode);
public static final ClickEventAction<SuggestCommandClickEvent> SUGGEST_COMMAND = define("suggest_command", true,
SuggestCommandClickEvent::decode, SuggestCommandClickEvent::encode);
/**
* Added with 1.8
*/
public static final ClickEventAction<ChangePageClickEvent> CHANGE_PAGE = define("change_page", true,
ChangePageClickEvent::decode, ChangePageClickEvent::encode);
/**
* Added with 1.15
*/
public static final ClickEventAction<CopyToClipboardClickEvent> COPY_TO_CLIPBOARD = define("copy_to_clipboard", true,
CopyToClipboardClickEvent::decode, CopyToClipboardClickEvent::encode);
/**
* Added with 1.21.6
*/
public static final ClickEventAction<ShowDialogClickEvent> SHOW_DIALOG = define("show_dialog", true,
ShowDialogClickEvent::decode, ShowDialogClickEvent::encode);
/**
* Added with 1.21.6
*/
public static final ClickEventAction<CustomClickEvent> CUSTOM = define("custom", true,
CustomClickEvent::decode, CustomClickEvent::encode);
static {
REGISTRY.unloadMappings();
}
}
@@ -0,0 +1,57 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class CopyToClipboardClickEvent implements ClickEvent {
private final String value;
public CopyToClipboardClickEvent(String value) {
this.value = value;
}
public static CopyToClipboardClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
String value = compound.getStringTagValueOrThrow("value");
return new CopyToClipboardClickEvent(value);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, CopyToClipboardClickEvent clickEvent) {
compound.setTag("value", new NBTString(clickEvent.value));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.COPY_TO_CLIPBOARD;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.copyToClipboard(this.value);
}
public String getValue() {
return this.value;
}
}
@@ -0,0 +1,72 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTEnd;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.adventure.NbtTagHolder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class CustomClickEvent implements ClickEvent {
private final ResourceLocation id;
private final @Nullable NBT payload;
public CustomClickEvent(ResourceLocation id, @Nullable NBT payload) {
this.id = id;
this.payload = payload;
}
public static CustomClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
ResourceLocation id = compound.getOrThrow("id", ResourceLocation::decode, wrapper);
NBT payload = compound.getTagOrNull("payload");
return new CustomClickEvent(id, payload);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, CustomClickEvent clickEvent) {
compound.set("id", clickEvent.id, ResourceLocation::encode, wrapper);
if (clickEvent.payload != null) {
compound.setTag("payload", clickEvent.payload);
}
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.CUSTOM;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.custom(
this.id.key(), new NbtTagHolder(this.payload != null ? this.payload : NBTEnd.INSTANCE));
}
public ResourceLocation getId() {
return this.id;
}
public @Nullable NBT getPayload() {
return this.payload;
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenFileClickEvent implements ClickEvent {
private final String path;
public OpenFileClickEvent(String path) {
this.path = path;
}
public static OpenFileClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
String path = compound.getStringTagValueOrThrow(v1215 ? "path" : "value");
return new OpenFileClickEvent(path);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, OpenFileClickEvent clickEvent) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
compound.setTag(v1215 ? "path" : "value", new NBTString(clickEvent.path));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.OPEN_FILE;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.openFile(this.path);
}
public String getPath() {
return this.path;
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class OpenUrlClickEvent implements ClickEvent {
private final String url;
public OpenUrlClickEvent(String url) {
this.url = url;
}
public static OpenUrlClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
String url = compound.getStringTagValueOrThrow(v1215 ? "url" : "value");
return new OpenUrlClickEvent(url);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, OpenUrlClickEvent clickEvent) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
compound.setTag(v1215 ? "url" : "value", new NBTString(clickEvent.url));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.OPEN_URL;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.openUrl(this.url);
}
public String getUrl() {
return this.url;
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class RunCommandClickEvent implements ClickEvent {
private final String command;
public RunCommandClickEvent(String command) {
this.command = command;
}
public static RunCommandClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
String command = compound.getStringTagValueOrThrow(v1215 ? "command" : "value");
return new RunCommandClickEvent(command);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, RunCommandClickEvent clickEvent) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
compound.setTag(v1215 ? "command" : "value", new NBTString(clickEvent.command));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.RUN_COMMAND;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.runCommand(this.command);
}
public String getCommand() {
return this.command;
}
}
@@ -0,0 +1,68 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.dialog.Dialog;
import com.github.retrooper.packetevents.protocol.dialog.Dialogs;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntityRef;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class ShowDialogClickEvent implements ClickEvent {
private final MappedEntityRef<Dialog> dialog;
public ShowDialogClickEvent(Dialog dialog) {
this(new MappedEntityRef.Static<>(dialog));
}
public ShowDialogClickEvent(MappedEntityRef<Dialog> dialog) {
this.dialog = dialog;
}
public static ShowDialogClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
MappedEntityRef<Dialog> dialog = MappedEntityRef.decode(compound.getTagOrThrow("dialog"),
Dialogs.getRegistry(), Dialog::decode, wrapper);
return new ShowDialogClickEvent(dialog);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, ShowDialogClickEvent clickEvent) {
compound.setTag("dialog", MappedEntityRef.encode(wrapper, Dialog::encode, clickEvent.dialog));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.SHOW_DIALOG;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.showDialog(this.dialog.get());
}
public MappedEntityRef<Dialog> getDialogRef() {
return this.dialog;
}
public Dialog getDialog() {
return this.dialog.get();
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticClickEventAction<T extends ClickEvent> extends AbstractMappedEntity implements ClickEventAction<T> {
private final boolean allowFromServer;
private final NbtMapDecoder<T> decoder;
private final NbtMapEncoder<T> encoder;
@ApiStatus.Internal
public StaticClickEventAction(
@Nullable TypesBuilderData data, boolean allowFromServer,
NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder
) {
super(data);
this.allowFromServer = allowFromServer;
this.decoder = decoder;
this.encoder = encoder;
}
@Override
public boolean isAllowFromServer() {
return this.allowFromServer;
}
@Override
public T decode(NBTCompound compound, PacketWrapper<?> wrapper) {
return this.decoder.decode(compound, wrapper);
}
@Override
public void encode(NBTCompound compound, PacketWrapper<?> wrapper, T clickEvent) {
this.encoder.encode(compound, wrapper, clickEvent);
}
}
@@ -0,0 +1,60 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class SuggestCommandClickEvent implements ClickEvent {
private final String command;
public SuggestCommandClickEvent(String command) {
this.command = command;
}
public static SuggestCommandClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
String command = compound.getStringTagValueOrThrow(v1215 ? "command" : "value");
return new SuggestCommandClickEvent(command);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, SuggestCommandClickEvent clickEvent) {
boolean v1215 = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5);
compound.setTag(v1215 ? "command" : "value", new NBTString(clickEvent.command));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.SUGGEST_COMMAND;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.suggestCommand(this.command);
}
public String getCommand() {
return this.command;
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.chat.clickevent;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Removed in 1.9
*/
@NullMarked
@ApiStatus.Obsolete
public class TwitchUserInfoClickEvent implements ClickEvent {
private final String value;
public TwitchUserInfoClickEvent(String value) {
this.value = value;
}
public static TwitchUserInfoClickEvent decode(NBTCompound compound, PacketWrapper<?> wrapper) {
String value = compound.getStringTagValueOrThrow("value");
return new TwitchUserInfoClickEvent(value);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, TwitchUserInfoClickEvent clickEvent) {
compound.setTag("value", new NBTString(clickEvent.value));
}
@Override
public ClickEventAction<?> getAction() {
return ClickEventActions.TWITCH_USER_INFO;
}
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
throw new UnsupportedOperationException();
}
public String getValue() {
return this.value;
}
}
@@ -19,6 +19,8 @@
package com.github.retrooper.packetevents.protocol.chat.message;
import com.github.retrooper.packetevents.protocol.chat.ChatType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import net.kyori.adventure.text.Component;
public class ChatMessage {
@@ -34,14 +36,22 @@ public class ChatMessage {
return chatContent;
}
public ChatType getType() {
return type;
public String getChatContentJson(ClientVersion version) {
return AdventureSerializer.serializer(version).asJson(this.getChatContent());
}
public void setChatContent(Component chatContent) {
this.chatContent = chatContent;
}
public void setChatContentJson(ClientVersion version, String json) {
this.setChatContent(AdventureSerializer.serializer(version).fromJson(json));
}
public ChatType getType() {
return type;
}
public void setType(ChatType type) {
this.type = type;
}
@@ -65,6 +65,16 @@ public class Color implements RGBLike {
wrapper.writeInt(color.asRGB());
}
public static Color readShort(PacketWrapper<?> wrapper) {
return new Color(wrapper.readUnsignedByte(), wrapper.readUnsignedByte(), wrapper.readUnsignedByte());
}
public static void writeShort(PacketWrapper<?> wrapper, Color color) {
wrapper.writeByte(color.red);
wrapper.writeByte(color.green);
wrapper.writeByte(color.blue);
}
public static Color decode(NBT nbt, ClientVersion version) {
if (nbt instanceof NBTNumber) {
return new Color(((NBTNumber) nbt).getAsInt());
@@ -22,14 +22,22 @@ import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.mappings.IRegistry;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Map;
import java.util.Optional;
@NullMarked
public interface IComponentMap {
static StaticComponentMap decode(NBT nbt, PacketWrapper<?> wrapper, IRegistry<? extends ComponentType<?>> registry) {
return decode(nbt, wrapper.getServerVersion().toClientVersion(), registry);
}
@Deprecated
@SuppressWarnings("unchecked") // safe in this case
static StaticComponentMap decode(NBT nbt, ClientVersion version, IRegistry<? extends ComponentType<?>> registry) {
NBTCompound compound = (NBTCompound) nbt;
@@ -45,6 +53,11 @@ public interface IComponentMap {
return components.build();
}
static NBT encode(PacketWrapper<?> wrapper, StaticComponentMap components) {
return encode(components, wrapper.getServerVersion().toClientVersion());
}
@Deprecated
@SuppressWarnings("unchecked") // safe in this case
static NBT encode(StaticComponentMap components, ClientVersion version) {
NBTCompound compound = new NBTCompound();
@@ -20,8 +20,10 @@ package com.github.retrooper.packetevents.protocol.component.builtin.item;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.attribute.Attribute;
import com.github.retrooper.packetevents.protocol.attribute.AttributeDisplay;
import com.github.retrooper.packetevents.protocol.attribute.AttributeOperation;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.attribute.DefaultAttributeDisplay;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes.PropertyModifier;
@@ -133,24 +135,39 @@ public class ItemAttributeModifiers {
private Attribute attribute;
private Modifier modifier;
private EquipmentSlotGroup slotGroup;
private AttributeDisplay display;
@ApiStatus.Obsolete
public ModifierEntry(Attribute attribute, Modifier modifier, EquipmentSlotGroup slotGroup) {
this(attribute, modifier, slotGroup, DefaultAttributeDisplay.INSTANCE);
}
public ModifierEntry(
Attribute attribute, Modifier modifier,
EquipmentSlotGroup slotGroup, AttributeDisplay display
) {
this.attribute = attribute;
this.modifier = modifier;
this.slotGroup = slotGroup;
this.display = display;
}
public static ModifierEntry read(PacketWrapper<?> wrapper) {
Attribute attribute = wrapper.readMappedEntity(Attributes::getById);
Modifier modifier = Modifier.read(wrapper);
EquipmentSlotGroup slot = wrapper.readEnum(EquipmentSlotGroup.values());
return new ModifierEntry(attribute, modifier, slot);
AttributeDisplay display = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_6)
? AttributeDisplay.read(wrapper) : DefaultAttributeDisplay.INSTANCE;
return new ModifierEntry(attribute, modifier, slot, display);
}
public static void write(PacketWrapper<?> wrapper, ModifierEntry entry) {
wrapper.writeMappedEntity(entry.attribute);
Modifier.write(wrapper, entry.modifier);
wrapper.writeEnum(entry.slotGroup);
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
AttributeDisplay.write(wrapper, entry.display);
}
}
public Attribute getAttribute() {
@@ -24,6 +24,7 @@ import com.github.retrooper.packetevents.protocol.entity.type.EntityTypes;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntitySet;
import com.github.retrooper.packetevents.protocol.player.EquipmentSlot;
import com.github.retrooper.packetevents.protocol.sound.Sound;
import com.github.retrooper.packetevents.protocol.sound.Sounds;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
@@ -41,7 +42,18 @@ public class ItemEquippable {
private boolean dispensable;
private boolean swappable;
private boolean damageOnHurt;
/**
* Added with 1.21.5
*/
private boolean equipOnInteract;
/**
* Added with 1.21.6
*/
private boolean canBeSheared;
/**
* Added with 1.21.6
*/
private Sound shearingSound;
@ApiStatus.Obsolete
public ItemEquippable(
@@ -58,6 +70,7 @@ public class ItemEquippable {
dispensable, swappable, damageOnHurt, false);
}
@ApiStatus.Obsolete
public ItemEquippable(
EquipmentSlot slot,
Sound equipSound,
@@ -68,6 +81,24 @@ public class ItemEquippable {
boolean swappable,
boolean damageOnHurt,
boolean equipOnInteract
) {
this(slot, equipSound, assetId, cameraOverlay, allowedEntities,
dispensable, swappable, damageOnHurt, equipOnInteract,
false, Sounds.ITEM_SHEARS_SNIP);
}
public ItemEquippable(
EquipmentSlot slot,
Sound equipSound,
@Nullable ResourceLocation assetId,
@Nullable ResourceLocation cameraOverlay,
@Nullable MappedEntitySet<EntityType> allowedEntities,
boolean dispensable,
boolean swappable,
boolean damageOnHurt,
boolean equipOnInteract,
boolean canBeSheared,
Sound shearingSound
) {
this.slot = slot;
this.equipSound = equipSound;
@@ -78,6 +109,8 @@ public class ItemEquippable {
this.swappable = swappable;
this.damageOnHurt = damageOnHurt;
this.equipOnInteract = equipOnInteract;
this.canBeSheared = canBeSheared;
this.shearingSound = shearingSound;
}
public static ItemEquippable read(PacketWrapper<?> wrapper) {
@@ -90,9 +123,19 @@ public class ItemEquippable {
boolean dispensable = wrapper.readBoolean();
boolean swappable = wrapper.readBoolean();
boolean damageOnHurt = wrapper.readBoolean();
boolean equipOnInteract = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5) && wrapper.readBoolean();
boolean equipOnInteract = false;
boolean canBeSheared = false;
Sound shearingSound = Sounds.ITEM_SHEARS_SNIP;
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5)) {
equipOnInteract = wrapper.readBoolean();
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
canBeSheared = wrapper.readBoolean();
shearingSound = Sound.read(wrapper);
}
}
return new ItemEquippable(slot, equipSound, assetId,
cameraOverlay, allowedEntities, dispensable, swappable, damageOnHurt, equipOnInteract);
cameraOverlay, allowedEntities, dispensable, swappable,
damageOnHurt, equipOnInteract, canBeSheared, shearingSound);
}
public static void write(PacketWrapper<?> wrapper, ItemEquippable equippable) {
@@ -106,6 +149,10 @@ public class ItemEquippable {
wrapper.writeBoolean(equippable.damageOnHurt);
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5)) {
wrapper.writeBoolean(equippable.equipOnInteract);
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
wrapper.writeBoolean(equippable.canBeSheared);
Sound.write(wrapper, equippable.shearingSound);
}
}
}
@@ -187,6 +234,34 @@ public class ItemEquippable {
this.equipOnInteract = equipOnInteract;
}
/**
* Added with 1.21.6
*/
public boolean isCanBeSheared() {
return this.canBeSheared;
}
/**
* Added with 1.21.6
*/
public void setCanBeSheared(boolean canBeSheared) {
this.canBeSheared = canBeSheared;
}
/**
* Added with 1.21.6
*/
public Sound getShearingSound() {
return this.shearingSound;
}
/**
* Added with 1.21.6
*/
public void setShearingSound(Sound shearingSound) {
this.shearingSound = shearingSound;
}
@Deprecated
public @Nullable ResourceLocation getModel() {
return this.assetId;
@@ -199,27 +274,28 @@ public class ItemEquippable {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ItemEquippable)) return false;
ItemEquippable that = (ItemEquippable) obj;
if (this.dispensable != that.dispensable) return false;
if (this.swappable != that.swappable) return false;
if (this.damageOnHurt != that.damageOnHurt) return false;
if (this.equipOnInteract != that.equipOnInteract) return false;
if (this.canBeSheared != that.canBeSheared) return false;
if (this.slot != that.slot) return false;
if (!Objects.equals(this.equipSound, that.equipSound)) return false;
if (!this.equipSound.equals(that.equipSound)) return false;
if (!Objects.equals(this.assetId, that.assetId)) return false;
if (!Objects.equals(this.cameraOverlay, that.cameraOverlay)) return false;
if (!Objects.equals(this.allowedEntities, that.allowedEntities)) return false;
return Objects.equals(this.equipOnInteract, that.equipOnInteract);
return this.shearingSound.equals(that.shearingSound);
}
@Override
public int hashCode() {
return Objects.hash(this.slot, this.equipSound, this.assetId, this.cameraOverlay, this.allowedEntities, this.dispensable, this.swappable, this.damageOnHurt, this.equipOnInteract);
return Objects.hash(this.slot, this.equipSound, this.assetId, this.cameraOverlay, this.allowedEntities, this.dispensable, this.swappable, this.damageOnHurt, this.equipOnInteract, this.canBeSheared, this.shearingSound);
}
@Override
public String toString() {
return "ItemEquippable{slot=" + this.slot + ", equipSound=" + this.equipSound + ", assetId=" + this.assetId + ", cameraOverlay=" + this.cameraOverlay + ", allowedEntities=" + this.allowedEntities + ", dispensable=" + this.dispensable + ", swappable=" + this.swappable + ", damageOnHurt=" + this.damageOnHurt + ", equipOnInteract=" + this.equipOnInteract + '}';
return "ItemEquippable{slot=" + this.slot + ", equipSound=" + this.equipSound + ", assetId=" + this.assetId + ", cameraOverlay=" + this.cameraOverlay + ", allowedEntities=" + this.allowedEntities + ", dispensable=" + this.dispensable + ", swappable=" + this.swappable + ", damageOnHurt=" + this.damageOnHurt + ", equipOnInteract=" + this.equipOnInteract + ", canBeSheared=" + this.canBeSheared + ", shearingSound=" + this.shearingSound + '}';
}
}
@@ -0,0 +1,121 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.body.DialogBody;
import com.github.retrooper.packetevents.protocol.dialog.input.Input;
import com.github.retrooper.packetevents.protocol.nbt.NBTByte;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.List;
@NullMarked
public final class CommonDialogData {
private final Component title;
private final @Nullable Component externalTitle;
private final boolean canCloseWithEscape;
private final boolean pause;
private final DialogAction afterAction;
private final List<DialogBody> body;
private final List<Input> inputs;
public CommonDialogData(
Component title, @Nullable Component externalTitle, boolean canCloseWithEscape, boolean pause,
DialogAction afterAction, List<DialogBody> body, List<Input> inputs
) {
if (pause && !afterAction.isWillUnpause()) {
throw new IllegalArgumentException("Dialogs that pause the game must use after_action "
+ "values that unpause it after user action!");
}
this.title = title;
this.externalTitle = externalTitle;
this.canCloseWithEscape = canCloseWithEscape;
this.pause = pause;
this.afterAction = afterAction;
this.body = body;
this.inputs = inputs;
}
public static CommonDialogData decode(NBTCompound compound, PacketWrapper<?> wrapper) {
Component title = compound.getOrThrow("title", AdventureSerializer.serializer(wrapper), wrapper);
Component externalTitle = compound.getOrNull("external_title", AdventureSerializer.serializer(wrapper), wrapper);
boolean canCloseWithEscape = compound.getBooleanOr("can_close_with_escape", true);
boolean pause = compound.getBooleanOr("pause", true);
DialogAction afterAction = compound.getOr("after_action", DialogAction::decode, DialogAction.CLOSE, wrapper);
List<DialogBody> body = compound.getListOrEmpty("body", DialogBody::decode, wrapper);
List<Input> inputs = compound.getListOrEmpty("inputs", Input::decode, wrapper);
return new CommonDialogData(title, externalTitle, canCloseWithEscape, pause, afterAction, body, inputs);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, CommonDialogData data) {
compound.set("title", data.title, AdventureSerializer.serializer(wrapper), wrapper);
if (data.externalTitle != null) {
compound.set("external_title", data.externalTitle, AdventureSerializer.serializer(wrapper), wrapper);
}
if (!data.canCloseWithEscape) {
compound.setTag("can_close_with_escape", new NBTByte(false));
}
if (!data.pause) {
compound.setTag("pause", new NBTByte(false));
}
if (data.afterAction != DialogAction.CLOSE) {
compound.set("after_action", data.afterAction, DialogAction::encode, wrapper);
}
if (!data.body.isEmpty()) {
compound.setCompactList("body", data.body, DialogBody::encode, wrapper);
}
if (!data.inputs.isEmpty()) {
compound.setList("inputs", data.inputs, Input::encode, wrapper);
}
}
public Component getTitle() {
return this.title;
}
public @Nullable Component getExternalTitle() {
return this.externalTitle;
}
public boolean isCanCloseWithEscape() {
return this.canCloseWithEscape;
}
public boolean isPause() {
return this.pause;
}
public DialogAction getAfterAction() {
return this.afterAction;
}
public List<DialogBody> getBody() {
return this.body;
}
public List<Input> getInputs() {
return this.inputs;
}
}
@@ -0,0 +1,103 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.button.ActionButton;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
@NullMarked
public class ConfirmationDialog extends AbstractMappedEntity implements Dialog {
private final CommonDialogData common;
private final ActionButton yesButton;
private final ActionButton noButton;
public ConfirmationDialog(CommonDialogData common, ActionButton yesButton, ActionButton noButton) {
this(null, common, yesButton, noButton);
}
@ApiStatus.Internal
public ConfirmationDialog(
@Nullable TypesBuilderData data, CommonDialogData common,
ActionButton yesButton, ActionButton noButton
) {
super(data);
this.common = common;
this.yesButton = yesButton;
this.noButton = noButton;
}
public static ConfirmationDialog decode(NBTCompound compound, PacketWrapper<?> wrapper) {
CommonDialogData common = CommonDialogData.decode(compound, wrapper);
ActionButton yesButton = compound.getOrThrow("yes", ActionButton::decode, wrapper);
ActionButton noButton = compound.getOrThrow("no", ActionButton::decode, wrapper);
return new ConfirmationDialog(null, common, yesButton, noButton);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, ConfirmationDialog dialog) {
CommonDialogData.encode(compound, wrapper, dialog.common);
compound.set("yes", dialog.yesButton, ActionButton::encode, wrapper);
compound.set("no", dialog.noButton, ActionButton::encode, wrapper);
}
@Override
public Dialog copy(@Nullable TypesBuilderData newData) {
return new ConfirmationDialog(newData, this.common, this.yesButton, this.noButton);
}
public CommonDialogData getCommon() {
return this.common;
}
public ActionButton getYesButton() {
return this.yesButton;
}
public ActionButton getNoButton() {
return this.noButton;
}
@Override
public DialogType<?> getType() {
return DialogTypes.CONFIRMATION;
}
@Override
public boolean deepEquals(Object obj) {
if (!(obj instanceof ConfirmationDialog)) return false;
if (!super.equals(obj)) return false;
ConfirmationDialog that = (ConfirmationDialog) obj;
if (!this.common.equals(that.common)) return false;
if (!this.yesButton.equals(that.yesButton)) return false;
return this.noButton.equals(that.noButton);
}
@Override
public int deepHashCode() {
return Objects.hash(super.hashCode(), this.common, this.yesButton, this.noButton);
}
}
@@ -0,0 +1,86 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.mapper.CopyableEntity;
import com.github.retrooper.packetevents.protocol.mapper.DeepComparableEntity;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.dialog.DialogLike;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface Dialog extends MappedEntity, DeepComparableEntity, CopyableEntity<Dialog>, DialogLike {
static Dialog read(PacketWrapper<?> wrapper) {
return wrapper.readMappedEntityOrDirect(Dialogs.getRegistry(), Dialog::readDirect);
}
static void write(PacketWrapper<?> wrapper, Dialog dialog) {
wrapper.writeMappedEntityOrDirect(dialog, Dialog::writeDirect);
}
static Dialog readDirect(PacketWrapper<?> wrapper) {
return decodeDirect(wrapper.readNBTRaw(), wrapper, null);
}
static void writeDirect(PacketWrapper<?> wrapper, Dialog dialog) {
wrapper.writeNBTRaw(encodeDirect(dialog, wrapper));
}
static Dialog decode(NBT nbt, PacketWrapper<?> wrapper) {
if (nbt instanceof NBTString) {
return wrapper.replaceRegistry(Dialogs.getRegistry()).getByNameOrThrow(((NBTString) nbt).getValue());
}
return decodeDirect(nbt, wrapper, null);
}
static NBT encode(PacketWrapper<?> wrapper, Dialog dialog) {
if (dialog.isRegistered()) {
return new NBTString(dialog.getName().toString());
}
return encodeDirect(dialog, wrapper);
}
@ApiStatus.Internal
static Dialog decodeDirect(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
String dialogTypeName = compound.getStringTagValueOrThrow("type");
DialogType<?> dialogType = DialogTypes.getRegistry().getByNameOrThrow(dialogTypeName);
// TODO pass down data instead of copying
return dialogType.decode(compound, wrapper).copy(data);
}
@ApiStatus.Internal
@SuppressWarnings("unchecked") // not unchecked
static NBT encodeDirect(Dialog dialog, PacketWrapper<?> wrapper) {
NBTCompound compound = new NBTCompound();
compound.setTag("type", new NBTString(dialog.getType().getName().toString()));
((DialogType<? super Dialog>) dialog.getType()).encode(compound, wrapper, dialog);
return compound;
}
DialogType<?> getType();
}
@@ -0,0 +1,61 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.util.adventure.AdventureIndexUtil;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.util.Index;
import org.jspecify.annotations.NullMarked;
@NullMarked
public enum DialogAction {
CLOSE("close", true),
NONE("none", false),
WAIT_FOR_RESPONSE("wait_for_response", true);
public static final Index<String, DialogAction> NAME_INDEX = Index.create(
DialogAction.class, DialogAction::getName);
private final String name;
private final boolean willUnpause;
DialogAction(String name, boolean willUnpause) {
this.name = name;
this.willUnpause = willUnpause;
}
public static DialogAction decode(NBT nbt, PacketWrapper<?> wrapper) {
return AdventureIndexUtil.indexValueOrThrow(NAME_INDEX, ((NBTString) nbt).getValue());
}
public static NBT encode(PacketWrapper<?> wrapper, DialogAction action) {
return new NBTString(action.name);
}
public String getName() {
return this.name;
}
public boolean isWillUnpause() {
return this.willUnpause;
}
}
@@ -0,0 +1,138 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.button.ActionButton;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntityRefSet;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntitySet;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
@NullMarked
public class DialogListDialog extends AbstractMappedEntity implements Dialog {
private final CommonDialogData common;
private final MappedEntityRefSet<Dialog> dialogs;
private final @Nullable ActionButton exitAction;
private final int columns;
private final int buttonWidth;
public DialogListDialog(
CommonDialogData common,
MappedEntityRefSet<Dialog> dialogs,
@Nullable ActionButton exitAction,
int columns, int buttonWidth
) {
this(null, common, dialogs, exitAction, columns, buttonWidth);
}
@ApiStatus.Internal
public DialogListDialog(
@Nullable TypesBuilderData data,
CommonDialogData common,
MappedEntityRefSet<Dialog> dialogs,
@Nullable ActionButton exitAction,
int columns, int buttonWidth
) {
super(data);
this.common = common;
this.dialogs = dialogs;
this.exitAction = exitAction;
this.columns = columns;
this.buttonWidth = buttonWidth;
}
public static DialogListDialog decode(NBTCompound compound, PacketWrapper<?> wrapper) {
CommonDialogData common = CommonDialogData.decode(compound, wrapper);
MappedEntityRefSet<Dialog> dialogs = compound.getOrThrow("dialogs", MappedEntitySet::decodeRefSet, wrapper);
ActionButton action = compound.getOrNull("exit_action", ActionButton::decode, wrapper);
int columns = compound.getNumberTagValueOrDefault("columns", 2).intValue();
int buttonWidth = compound.getNumberTagValueOrDefault("button_width", 150).intValue();
return new DialogListDialog(null, common, dialogs, action, columns, buttonWidth);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, DialogListDialog dialog) {
CommonDialogData.encode(compound, wrapper, dialog.common);
compound.set("dialogs", dialog.dialogs, MappedEntitySet::encodeRefSet, wrapper);
if (dialog.exitAction != null) {
compound.set("exit_action", dialog.exitAction, ActionButton::encode, wrapper);
}
if (dialog.columns != 2) {
compound.setTag("columns", new NBTInt(dialog.columns));
}
if (dialog.buttonWidth != 150) {
compound.setTag("button_width", new NBTInt(dialog.buttonWidth));
}
}
@Override
public Dialog copy(@Nullable TypesBuilderData newData) {
return new DialogListDialog(newData, this.common, this.dialogs, this.exitAction, this.columns, this.buttonWidth);
}
public CommonDialogData getCommon() {
return this.common;
}
public MappedEntityRefSet<Dialog> getDialogs() {
return this.dialogs;
}
public @Nullable ActionButton getExitAction() {
return this.exitAction;
}
public int getColumns() {
return this.columns;
}
public int getButtonWidth() {
return this.buttonWidth;
}
@Override
public DialogType<?> getType() {
return DialogTypes.DIALOG_LIST;
}
@Override
public boolean deepEquals(Object obj) {
if (!(obj instanceof DialogListDialog)) return false;
if (!super.equals(obj)) return false;
DialogListDialog that = (DialogListDialog) obj;
if (this.columns != that.columns) return false;
if (this.buttonWidth != that.buttonWidth) return false;
if (!this.common.equals(that.common)) return false;
if (!this.dialogs.equals(that.dialogs)) return false;
return Objects.equals(this.exitAction, that.exitAction);
}
@Override
public int deepHashCode() {
return Objects.hash(super.hashCode(), this.common, this.dialogs, this.exitAction, this.columns, this.buttonWidth);
}
}
@@ -0,0 +1,32 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface DialogType<T extends Dialog> extends MappedEntity {
T decode(NBTCompound compound, PacketWrapper<?> wrapper);
void encode(NBTCompound compound, PacketWrapper<?> wrapper, T dialog);
}
@@ -0,0 +1,58 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class DialogTypes {
private static final VersionedRegistry<DialogType<?>> REGISTRY = new VersionedRegistry<>("dialog_type");
private DialogTypes() {
}
public static VersionedRegistry<DialogType<?>> getRegistry() {
return REGISTRY;
}
@ApiStatus.Internal
public static <T extends Dialog> DialogType<T> define(String name, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder) {
return REGISTRY.define(name, data -> new StaticDialogType<>(data, decoder, encoder));
}
public static final DialogType<NoticeDialog> NOTICE = define("notice",
NoticeDialog::decode, NoticeDialog::encode);
public static final DialogType<ServerLinksDialog> SERVER_LINKS = define("server_links",
ServerLinksDialog::decode, ServerLinksDialog::encode);
public static final DialogType<DialogListDialog> DIALOG_LIST = define("dialog_list",
DialogListDialog::decode, DialogListDialog::encode);
public static final DialogType<MultiActionDialog> MULTI_ACTION = define("multi_action",
MultiActionDialog::decode, MultiActionDialog::encode);
public static final DialogType<ConfirmationDialog> CONFIRMATION = define("confirmation",
ConfirmationDialog::decode, ConfirmationDialog::encode);
static {
REGISTRY.unloadMappings();
}
}
@@ -0,0 +1,101 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.button.ActionButton;
import com.github.retrooper.packetevents.protocol.dialog.button.CommonButtonData;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntitySet;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.util.Collections;
@NullMarked
public final class Dialogs {
private static final ActionButton DEFAULT_BACK_BUTTON = new ActionButton(
new CommonButtonData(Component.translatable("gui.back"), null, 200),
null);
private static final VersionedRegistry<Dialog> REGISTRY = new VersionedRegistry<>("dialog");
private Dialogs() {
}
@ApiStatus.Internal
public static Dialog define(String name, Dialog dialog) {
return REGISTRY.define(name, dialog::copy);
}
public static VersionedRegistry<Dialog> getRegistry() {
return REGISTRY;
}
public static final Dialog SERVER_LINKS = define("server_links", new ServerLinksDialog(
new CommonDialogData(
Component.translatable("menu.server_links.title"),
Component.translatable("menu.server_links"),
true,
true,
DialogAction.CLOSE,
Collections.emptyList(),
Collections.emptyList()
),
DEFAULT_BACK_BUTTON,
1,
310
));
public static final Dialog CUSTOM_OPTIONS = define("custom_options", new DialogListDialog(
new CommonDialogData(
Component.translatable("menu.custom_options.title"),
Component.translatable("menu.custom_options"),
true,
true,
DialogAction.CLOSE,
Collections.emptyList(),
Collections.emptyList()
),
new MappedEntitySet<>(new ResourceLocation("pause_screen_additions")),
DEFAULT_BACK_BUTTON,
1,
310
));
public static final Dialog QUICK_ACTIONS = define("quick_actions", new DialogListDialog(
new CommonDialogData(
Component.translatable("menu.quick_actions.title"),
Component.translatable("menu.quick_actions"),
true,
true,
DialogAction.CLOSE,
Collections.emptyList(),
Collections.emptyList()
),
new MappedEntitySet<>(new ResourceLocation("quick_actions")),
DEFAULT_BACK_BUTTON,
1,
310
));
static {
REGISTRY.unloadMappings();
}
}
@@ -0,0 +1,127 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.button.ActionButton;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.List;
import java.util.Objects;
@NullMarked
public class MultiActionDialog extends AbstractMappedEntity implements Dialog {
private final CommonDialogData common;
private final List<ActionButton> actions;
private final @Nullable ActionButton exitAction;
private final int columns;
public MultiActionDialog(
CommonDialogData common,
List<ActionButton> actions,
@Nullable ActionButton exitAction,
int columns
) {
this(null, common, actions, exitAction, columns);
}
@ApiStatus.Internal
public MultiActionDialog(
@Nullable TypesBuilderData data,
CommonDialogData common,
List<ActionButton> actions,
@Nullable ActionButton exitAction,
int columns
) {
super(data);
this.common = common;
this.actions = actions;
this.exitAction = exitAction;
this.columns = columns;
}
public static MultiActionDialog decode(NBTCompound compound, PacketWrapper<?> wrapper) {
CommonDialogData common = CommonDialogData.decode(compound, wrapper);
List<ActionButton> actions = compound.getListOrThrow("actions", ActionButton::decode, wrapper);
ActionButton action = compound.getOrNull("exit_action", ActionButton::decode, wrapper);
int columns = compound.getNumberTagValueOrDefault("columns", 2).intValue();
int buttonWidth = compound.getNumberTagValueOrDefault("button_width", 150).intValue();
return new MultiActionDialog(null, common, actions, action, columns);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, MultiActionDialog dialog) {
CommonDialogData.encode(compound, wrapper, dialog.common);
compound.setList("actions", dialog.actions, ActionButton::encode, wrapper);
if (dialog.exitAction != null) {
compound.set("exit_action", dialog.exitAction, ActionButton::encode, wrapper);
}
if (dialog.columns != 2) {
compound.setTag("columns", new NBTInt(dialog.columns));
}
}
@Override
public Dialog copy(@Nullable TypesBuilderData newData) {
return new MultiActionDialog(newData, this.common, this.actions, this.exitAction, this.columns);
}
public CommonDialogData getCommon() {
return this.common;
}
public List<ActionButton> getActions() {
return this.actions;
}
public @Nullable ActionButton getExitAction() {
return this.exitAction;
}
public int getColumns() {
return this.columns;
}
@Override
public DialogType<?> getType() {
return DialogTypes.MULTI_ACTION;
}
@Override
public boolean deepEquals(Object obj) {
if (!(obj instanceof MultiActionDialog)) return false;
if (!super.equals(obj)) return false;
MultiActionDialog that = (MultiActionDialog) obj;
if (this.columns != that.columns) return false;
if (!this.common.equals(that.common)) return false;
if (!this.actions.equals(that.actions)) return false;
return Objects.equals(this.exitAction, that.exitAction);
}
@Override
public int deepHashCode() {
return Objects.hash(super.hashCode(), this.common, this.actions, this.exitAction, this.columns);
}
}
@@ -0,0 +1,100 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.button.ActionButton;
import com.github.retrooper.packetevents.protocol.dialog.button.CommonButtonData;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
@NullMarked
public class NoticeDialog extends AbstractMappedEntity implements Dialog {
public static final ActionButton DEFAULT_ACTION = new ActionButton(
new CommonButtonData(Component.translatable("gui.ok"), null, 150),
null
);
private final CommonDialogData common;
private final ActionButton action;
public NoticeDialog(CommonDialogData common, ActionButton action) {
this(null, common, action);
}
@ApiStatus.Internal
public NoticeDialog(@Nullable TypesBuilderData data, CommonDialogData common, ActionButton action) {
super(data);
this.common = common;
this.action = action;
}
public static NoticeDialog decode(NBTCompound compound, PacketWrapper<?> wrapper) {
CommonDialogData common = CommonDialogData.decode(compound, wrapper);
ActionButton action = compound.getOr("action", ActionButton::decode, DEFAULT_ACTION, wrapper);
return new NoticeDialog(null, common, action);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, NoticeDialog dialog) {
CommonDialogData.encode(compound, wrapper, dialog.common);
if (dialog.action != DEFAULT_ACTION) {
compound.set("action", dialog.action, ActionButton::encode, wrapper);
}
}
@Override
public Dialog copy(@Nullable TypesBuilderData newData) {
return new NoticeDialog(newData, this.common, this.action);
}
public CommonDialogData getCommon() {
return this.common;
}
public ActionButton getAction() {
return this.action;
}
@Override
public DialogType<?> getType() {
return DialogTypes.NOTICE;
}
@Override
public boolean deepEquals(Object obj) {
if (!(obj instanceof NoticeDialog)) return false;
if (!super.equals(obj)) return false;
NoticeDialog that = (NoticeDialog) obj;
if (!this.common.equals(that.common)) return false;
return this.action.equals(that.action);
}
@Override
public int deepHashCode() {
return Objects.hash(super.hashCode(), this.common, this.action);
}
}
@@ -0,0 +1,125 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.dialog.button.ActionButton;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
@NullMarked
public class ServerLinksDialog extends AbstractMappedEntity implements Dialog {
private final CommonDialogData common;
private final @Nullable ActionButton exitAction;
private final int columns;
private final int buttonWidth;
public ServerLinksDialog(
CommonDialogData common,
@Nullable ActionButton exitAction,
int columns, int buttonWidth
) {
this(null, common, exitAction, columns, buttonWidth);
}
@ApiStatus.Internal
public ServerLinksDialog(
@Nullable TypesBuilderData data,
CommonDialogData common,
@Nullable ActionButton exitAction,
int columns, int buttonWidth
) {
super(data);
this.common = common;
this.exitAction = exitAction;
this.columns = columns;
this.buttonWidth = buttonWidth;
}
public static ServerLinksDialog decode(NBTCompound compound, PacketWrapper<?> wrapper) {
CommonDialogData common = CommonDialogData.decode(compound, wrapper);
ActionButton action = compound.getOrNull("exit_action", ActionButton::decode, wrapper);
int columns = compound.getNumberTagValueOrDefault("columns", 2).intValue();
int buttonWidth = compound.getNumberTagValueOrDefault("button_width", 150).intValue();
return new ServerLinksDialog(null, common, action, columns, buttonWidth);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, ServerLinksDialog dialog) {
CommonDialogData.encode(compound, wrapper, dialog.common);
if (dialog.exitAction != null) {
compound.set("exit_action", dialog.exitAction, ActionButton::encode, wrapper);
}
if (dialog.columns != 2) {
compound.setTag("columns", new NBTInt(dialog.columns));
}
if (dialog.buttonWidth != 150) {
compound.setTag("button_width", new NBTInt(dialog.buttonWidth));
}
}
@Override
public Dialog copy(@Nullable TypesBuilderData newData) {
return new ServerLinksDialog(newData, this.common, this.exitAction, this.columns, this.buttonWidth);
}
public CommonDialogData getCommon() {
return this.common;
}
public @Nullable ActionButton getExitAction() {
return this.exitAction;
}
public int getColumns() {
return this.columns;
}
public int getButtonWidth() {
return this.buttonWidth;
}
@Override
public DialogType<?> getType() {
return DialogTypes.SERVER_LINKS;
}
@Override
public boolean deepEquals(Object obj) {
if (!(obj instanceof ServerLinksDialog)) return false;
if (!super.equals(obj)) return false;
ServerLinksDialog that = (ServerLinksDialog) obj;
if (this.columns != that.columns) return false;
if (this.buttonWidth != that.buttonWidth) return false;
if (!this.common.equals(that.common)) return false;
return Objects.equals(this.exitAction, that.exitAction);
}
@Override
public int deepHashCode() {
return Objects.hash(super.hashCode(), this.common, this.exitAction, this.columns, this.buttonWidth);
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticDialogType<T extends Dialog> extends AbstractMappedEntity implements DialogType<T> {
private final NbtMapDecoder<T> decoder;
private final NbtMapEncoder<T> encoder;
@ApiStatus.Internal
public StaticDialogType(@Nullable TypesBuilderData data, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder) {
super(data);
this.decoder = decoder;
this.encoder = encoder;
}
@Override
public T decode(NBTCompound compound, PacketWrapper<?> wrapper) {
return this.decoder.decode(compound, wrapper);
}
@Override
public void encode(NBTCompound compound, PacketWrapper<?> wrapper, T dialog) {
this.encoder.encode(compound, wrapper, dialog);
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface Action {
static Action decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
String typeName = compound.getStringTagValueOrThrow("type");
ActionType<?> action = ActionTypes.getRegistry().getByNameOrThrow(typeName);
return action.decode(compound, wrapper);
}
@SuppressWarnings("unchecked") // not unchecked
static NBT encode(PacketWrapper<?> wrapper, Action action) {
NBTCompound compound = new NBTCompound();
compound.set("type", action.getType().getName(), ResourceLocation::encode, wrapper);
((ActionType<? super Action>) action.getType()).encode(compound, wrapper, action);
return compound;
}
ActionType<?> getType();
}
@@ -0,0 +1,32 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface ActionType<T extends Action> extends MappedEntity {
T decode(NBTCompound compound, PacketWrapper<?> wrapper);
void encode(NBTCompound compound, PacketWrapper<?> wrapper, T action);
}
@@ -0,0 +1,66 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class ActionTypes {
private static final VersionedRegistry<ActionType<?>> REGISTRY = new VersionedRegistry<>("dialog_action_type");
private ActionTypes() {
}
@ApiStatus.Internal
public static <T extends Action> ActionType<T> define(String name, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder) {
return REGISTRY.define(name, data -> new StaticActionType<>(data, decoder, encoder));
}
public static final ActionType<StaticAction> OPEN_URL = define("open_url",
StaticAction::decode, StaticAction::encode);
public static final ActionType<StaticAction> RUN_COMMAND = define("run_command",
StaticAction::decode, StaticAction::encode);
public static final ActionType<StaticAction> SUGGEST_COMMAND = define("suggest_command",
StaticAction::decode, StaticAction::encode);
public static final ActionType<StaticAction> SHOW_DIALOG = define("show_dialog",
StaticAction::decode, StaticAction::encode);
public static final ActionType<StaticAction> CHANGE_PAGE = define("change_page",
StaticAction::decode, StaticAction::encode);
public static final ActionType<StaticAction> COPY_TO_CLIPBOARD = define("copy_to_clipboard",
StaticAction::decode, StaticAction::encode);
public static final ActionType<StaticAction> CUSTOM = define("custom",
StaticAction::decode, StaticAction::encode);
public static final ActionType<DynamicRunCommandAction> DYNAMIC_RUN_COMMAND = define("dynamic/run_command",
DynamicRunCommandAction::decode, DynamicRunCommandAction::encode);
public static final ActionType<DynamicCustomAction> DYNAMIC_CUSTOM = define("dynamic/custom",
DynamicCustomAction::decode, DynamicCustomAction::encode);
public static VersionedRegistry<ActionType<?>> getRegistry() {
return REGISTRY;
}
static {
REGISTRY.unloadMappings();
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
/**
* This uses the same format as command function macros.
*/
@NullMarked
public class DialogTemplate {
private final String raw;
public DialogTemplate(String raw) {
this.raw = raw;
}
public static DialogTemplate decode(NBT nbt, PacketWrapper<?> wrapper) {
return new DialogTemplate(((NBTString) nbt).getValue());
}
public static NBT encode(PacketWrapper<?> wrapper, DialogTemplate template) {
return new NBTString(template.raw);
}
public String getRaw() {
return this.raw;
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class DynamicCustomAction implements Action {
private final ResourceLocation id;
private final @Nullable NBTCompound additions;
public DynamicCustomAction(ResourceLocation id, @Nullable NBTCompound additions) {
this.id = id;
this.additions = additions;
}
public static DynamicCustomAction decode(NBTCompound compound, PacketWrapper<?> wrapper) {
ResourceLocation id = compound.getOrThrow("id", ResourceLocation::decode, wrapper);
NBTCompound additions = compound.getCompoundTagOrNull("additions");
return new DynamicCustomAction(id, additions);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, DynamicCustomAction action) {
compound.set("id", action.id, ResourceLocation::encode, wrapper);
if (action.additions != null) {
compound.setTag("additions", action.additions);
}
}
@Override
public ActionType<?> getType() {
return ActionTypes.DYNAMIC_CUSTOM;
}
public ResourceLocation getId() {
return this.id;
}
public @Nullable NBTCompound getAdditions() {
return this.additions;
}
}
@@ -0,0 +1,51 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class DynamicRunCommandAction implements Action {
private final DialogTemplate template;
public DynamicRunCommandAction(DialogTemplate template) {
this.template = template;
}
public static DynamicRunCommandAction decode(NBTCompound compound, PacketWrapper<?> wrapper) {
DialogTemplate template = compound.getOrThrow("template", DialogTemplate::decode, wrapper);
return new DynamicRunCommandAction(template);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, DynamicRunCommandAction action) {
compound.set("template", action.template, DialogTemplate::encode, wrapper);
}
public DialogTemplate getTemplate() {
return this.template;
}
@Override
public ActionType<?> getType() {
return ActionTypes.DYNAMIC_RUN_COMMAND;
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.chat.clickevent.ClickEvent;
import com.github.retrooper.packetevents.protocol.chat.clickevent.ClickEventAction;
import com.github.retrooper.packetevents.protocol.chat.clickevent.ClickEventActions;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticAction implements Action {
private final ActionType<?> actionType;
private final ClickEvent clickEvent;
public StaticAction(ClickEvent clickEvent) {
if (!clickEvent.getAction().isAllowFromServer()) {
throw new IllegalArgumentException("Can't create action for unreadable "
+ "click event with action " + clickEvent.getAction());
}
this.actionType = ActionTypes.getRegistry().getByNameOrThrow(clickEvent.getAction().getName());
this.clickEvent = clickEvent;
}
public static StaticAction decode(NBTCompound compound, PacketWrapper<?> wrapper) {
String actionName = compound.getStringTagValueOrThrow("type");
ClickEventAction<?> action = ClickEventActions.getRegistry().getByNameOrThrow(actionName);
ClickEvent clickEvent = action.decode(compound, wrapper);
return new StaticAction(clickEvent);
}
@SuppressWarnings("unchecked")
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, StaticAction action) {
((ClickEventAction<? super ClickEvent>) action.clickEvent.getAction()).encode(compound, wrapper, action.clickEvent);
}
@Override
public ActionType<?> getType() {
return this.actionType;
}
public ClickEvent getClickEvent() {
return this.clickEvent;
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.action;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticActionType<T extends Action> extends AbstractMappedEntity implements ActionType<T> {
private final NbtMapDecoder<T> decoder;
private final NbtMapEncoder<T> encoder;
@ApiStatus.Internal
public StaticActionType(@Nullable TypesBuilderData data, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder) {
super(data);
this.decoder = decoder;
this.encoder = encoder;
}
@Override
public T decode(NBTCompound compound, PacketWrapper<?> wrapper) {
return this.decoder.decode(compound, wrapper);
}
@Override
public void encode(NBTCompound compound, PacketWrapper<?> wrapper, T action) {
this.encoder.encode(compound, wrapper, action);
}
}
@@ -0,0 +1,46 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface DialogBody {
static DialogBody decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
String typeName = compound.getStringTagValueOrThrow("type");
DialogBodyType<?> type = DialogBodyTypes.getRegistry().getByNameOrThrow(typeName);
return type.decode(compound, wrapper);
}
@SuppressWarnings("unchecked") // not unchecked
static NBT encode(PacketWrapper<?> wrapper, DialogBody body) {
NBTCompound compound = new NBTCompound();
compound.set("type", body.getType().getName(), ResourceLocation::encode, wrapper);
((DialogBodyType<? super DialogBody>) body.getType()).encode(compound, wrapper, body);
return compound;
}
DialogBodyType<?> getType();
}
@@ -0,0 +1,32 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface DialogBodyType<T extends DialogBody> extends MappedEntity {
T decode(NBTCompound compound, PacketWrapper<?> wrapper);
void encode(NBTCompound compound, PacketWrapper<?> wrapper, T dialogBody);
}
@@ -0,0 +1,52 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class DialogBodyTypes {
private static final VersionedRegistry<DialogBodyType<?>> REGISTRY = new VersionedRegistry<>("dialog_body_type");
private DialogBodyTypes() {
}
@ApiStatus.Internal
public static <T extends DialogBody> DialogBodyType<T> define(String name, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder) {
return REGISTRY.define(name, data -> new StaticDialogBodyType<>(data, decoder, encoder));
}
public static VersionedRegistry<DialogBodyType<?>> getRegistry() {
return REGISTRY;
}
public static final DialogBodyType<ItemDialogBody> ITEM = define("item",
ItemDialogBody::decode, ItemDialogBody::encode);
public static final DialogBodyType<PlainMessageDialogBody> PLAIN_MESSAGE = define("plain_message",
PlainMessageDialogBody::decode, PlainMessageDialogBody::encode);
static {
REGISTRY.unloadMappings();
}
}
@@ -0,0 +1,109 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.item.ItemStack;
import com.github.retrooper.packetevents.protocol.nbt.NBTByte;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class ItemDialogBody implements DialogBody {
private final ItemStack item;
private final @Nullable PlainMessage description;
private final boolean showDecorations;
private final boolean showTooltip;
private final int width;
private final int height;
public ItemDialogBody(
ItemStack item, @Nullable PlainMessage description,
boolean showDecorations, boolean showTooltip,
int width, int height
) {
this.item = item;
this.description = description;
this.showDecorations = showDecorations;
this.showTooltip = showTooltip;
this.width = width;
this.height = height;
}
public static ItemDialogBody decode(NBTCompound compound, PacketWrapper<?> wrapper) {
ItemStack item = compound.getOrThrow("item", ItemStack::decode, wrapper);
PlainMessage description = compound.getOrNull("description", PlainMessage::decode, wrapper);
boolean showDecorations = compound.getBooleanOr("show_decorations", true);
boolean showTooltip = compound.getBooleanOr("show_tooltip", true);
int width = compound.getNumberTagValueOrDefault("width", 16).intValue();
int height = compound.getNumberTagValueOrDefault("height", 16).intValue();
return new ItemDialogBody(item, description, showDecorations, showTooltip, width, height);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, ItemDialogBody body) {
compound.set("item", body.item, ItemStack::encode, wrapper);
if (body.description != null) {
compound.set("description", body.description, PlainMessage::encode, wrapper);
}
if (!body.showDecorations) {
compound.setTag("show_decorations", new NBTByte(false));
}
if (!body.showTooltip) {
compound.setTag("show_tooltip", new NBTByte(false));
}
if (body.width != 16) {
compound.setTag("width", new NBTInt(body.width));
}
if (body.height != 16) {
compound.setTag("height", new NBTInt(body.height));
}
}
@Override
public DialogBodyType<?> getType() {
return DialogBodyTypes.ITEM;
}
public ItemStack getItem() {
return this.item;
}
public @Nullable PlainMessage getDescription() {
return this.description;
}
public boolean isShowDecorations() {
return this.showDecorations;
}
public boolean isShowTooltip() {
return this.showTooltip;
}
public int getWidth() {
return this.width;
}
public int getHeight() {
return this.height;
}
}
@@ -0,0 +1,70 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PlainMessage {
private final Component contents;
private final int width;
public PlainMessage(Component contents, int width) {
this.contents = contents;
this.width = width;
}
public static PlainMessage decode(NBT nbt, PacketWrapper<?> wrapper) {
return decode((NBTCompound) nbt, wrapper);
}
public static PlainMessage decode(NBTCompound compound, PacketWrapper<?> wrapper) {
Component contents = compound.getOrThrow("contents", AdventureSerializer.serializer(wrapper), wrapper);
int width = compound.getNumberTagValueOrDefault("width", 200).intValue();
return new PlainMessage(contents, width);
}
public static NBT encode(PacketWrapper<?> wrapper, PlainMessage message) {
NBTCompound compound = new NBTCompound();
encode(compound, wrapper, message);
return compound;
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, PlainMessage message) {
compound.set("contents", message.contents, AdventureSerializer.serializer(wrapper), wrapper);
if (message.width != 200) {
compound.setTag("width", new NBTInt(message.width));
}
}
public Component getContents() {
return this.contents;
}
public int getWidth() {
return this.width;
}
}
@@ -0,0 +1,50 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PlainMessageDialogBody implements DialogBody {
private final PlainMessage message;
public PlainMessageDialogBody(PlainMessage message) {
this.message = message;
}
public static PlainMessageDialogBody decode(NBTCompound compound, PacketWrapper<?> wrapper) {
return new PlainMessageDialogBody(PlainMessage.decode(compound, wrapper));
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, PlainMessageDialogBody body) {
PlainMessage.encode(compound, wrapper, body.message);
}
@Override
public DialogBodyType<?> getType() {
return DialogBodyTypes.PLAIN_MESSAGE;
}
public PlainMessage getMessage() {
return this.message;
}
}
@@ -0,0 +1,53 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.body;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticDialogBodyType<T extends DialogBody> extends AbstractMappedEntity implements DialogBodyType<T> {
private final NbtMapDecoder<T> decoder;
private final NbtMapEncoder<T> encoder;
@ApiStatus.Internal
public StaticDialogBodyType(@Nullable TypesBuilderData data, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder) {
super(data);
this.decoder = decoder;
this.encoder = encoder;
}
@Override
public T decode(NBTCompound compound, PacketWrapper<?> wrapper) {
return this.decoder.decode(compound, wrapper);
}
@Override
public void encode(NBTCompound compound, PacketWrapper<?> wrapper, T dialogBody) {
this.encoder.encode(compound, wrapper, dialogBody);
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.button;
import com.github.retrooper.packetevents.protocol.dialog.action.Action;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class ActionButton {
private final CommonButtonData button;
private final @Nullable Action action;
public ActionButton(CommonButtonData button, @Nullable Action action) {
this.button = button;
this.action = action;
}
public static ActionButton decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
CommonButtonData button = CommonButtonData.decode(compound, wrapper);
Action action = compound.getOrNull("action", Action::decode, wrapper);
return new ActionButton(button, action);
}
public static NBT encode(PacketWrapper<?> wrapper, ActionButton button) {
NBTCompound compound = new NBTCompound();
CommonButtonData.encode(compound, wrapper, button.button);
if (button.action != null) {
compound.set("action", button.action, Action::encode, wrapper);
}
return compound;
}
public CommonButtonData getButton() {
return this.button;
}
public @Nullable Action getAction() {
return this.action;
}
}
@@ -0,0 +1,72 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.button;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class CommonButtonData {
private final Component label;
private final @Nullable Component tooltip;
private final int width;
public CommonButtonData(Component label, @Nullable Component tooltip, int width) {
this.label = label;
this.tooltip = tooltip;
this.width = width;
}
public static CommonButtonData decode(NBTCompound compound, PacketWrapper<?> wrapper) {
AdventureSerializer serializer = AdventureSerializer.serializer(wrapper);
Component label = compound.getOrThrow("label", serializer, wrapper);
Component tooltip = compound.getOrNull("tooltip", serializer, wrapper);
int width = compound.getNumberTagValueOrDefault("width", 150).intValue();
return new CommonButtonData(label, tooltip, width);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, CommonButtonData button) {
AdventureSerializer serializer = AdventureSerializer.serializer(wrapper);
compound.set("label", button.label, serializer, wrapper);
if (button.tooltip != null) {
compound.set("tooltip", button.tooltip, serializer, wrapper);
}
if (button.width != 150) {
compound.setTag("width", new NBTInt(button.width));
}
}
public Component getLabel() {
return this.label;
}
public @Nullable Component getTooltip() {
return this.tooltip;
}
public int getWidth() {
return this.width;
}
}
@@ -0,0 +1,85 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.nbt.NBTByte;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class BooleanInputControl implements InputControl {
private final Component label;
private final boolean initial;
private final String onTrue;
private final String onFalse;
public BooleanInputControl(Component label, boolean initial, String onTrue, String onFalse) {
this.label = label;
this.initial = initial;
this.onTrue = onTrue;
this.onFalse = onFalse;
}
public static BooleanInputControl decode(NBTCompound compound, PacketWrapper<?> wrapper) {
Component label = compound.getOrThrow("label", AdventureSerializer.serializer(wrapper), wrapper);
boolean initial = compound.getBoolean("initial");
String onTrue = compound.getStringTagValueOrDefault("on_true", "true");
String onFalse = compound.getStringTagValueOrDefault("on_false", "false");
return new BooleanInputControl(label, initial, onTrue, onFalse);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, BooleanInputControl control) {
compound.set("label", control.label, AdventureSerializer.serializer(wrapper), wrapper);
if (control.initial) {
compound.setTag("initial", new NBTByte(true));
}
if (!"true".equals(control.onTrue)) {
compound.setTag("on_true", new NBTString(control.onTrue));
}
if (!"false".equals(control.onFalse)) {
compound.setTag("on_false", new NBTString(control.onFalse));
}
}
@Override
public InputControlType<?> getType() {
return InputControlTypes.BOOLEAN;
}
public Component getLabel() {
return this.label;
}
public boolean isInitial() {
return this.initial;
}
public String getOnTrue() {
return this.onTrue;
}
public String getOnFalse() {
return this.onFalse;
}
}
@@ -0,0 +1,59 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class Input {
private final String key;
private final InputControl control;
public Input(String key, InputControl control) {
this.key = key;
this.control = control;
}
public static Input decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
String key = compound.getStringTagValueOrThrow("key");
InputControl control = InputControl.decode(compound, wrapper);
return new Input(key, control);
}
public static NBT encode(PacketWrapper<?> wrapper, Input input) {
NBTCompound compound = new NBTCompound();
compound.setTag("key", new NBTString(input.key));
InputControl.encode(compound, wrapper, input.control);
return compound;
}
public String getKey() {
return this.key;
}
public InputControl getControl() {
return this.control;
}
}
@@ -0,0 +1,42 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface InputControl {
static InputControl decode(NBTCompound compound, PacketWrapper<?> wrapper) {
String typeName = compound.getStringTagValueOrThrow("type");
InputControlType<?> type = InputControlTypes.getRegistry().getByNameOrThrow(typeName);
return type.decode(compound, wrapper);
}
@SuppressWarnings("unchecked") // not unchecked
static void encode(NBTCompound compound, PacketWrapper<?> wrapper, InputControl control) {
compound.set("type", control.getType().getName(), ResourceLocation::encode, wrapper);
((InputControlType<? super InputControl>) control.getType()).encode(compound, wrapper, control);
}
InputControlType<?> getType();
}
@@ -0,0 +1,32 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface InputControlType<T extends InputControl> extends MappedEntity {
T decode(NBTCompound compound, PacketWrapper<?> wrapper);
void encode(NBTCompound compound, PacketWrapper<?> wrapper, T control);
}
@@ -0,0 +1,59 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class InputControlTypes {
private static final VersionedRegistry<InputControlType<?>> REGISTRY = new VersionedRegistry<>("input_control_type");
private InputControlTypes() {
}
public static VersionedRegistry<InputControlType<?>> getRegistry() {
return REGISTRY;
}
@ApiStatus.Internal
public static <T extends InputControl> InputControlType<T> define(
String name, NbtMapDecoder<T> decoder, NbtMapEncoder<T> encoder
) {
return REGISTRY.define(name, data ->
new StaticInputControlType<>(data, decoder, encoder));
}
public static final InputControlType<BooleanInputControl> BOOLEAN = define("boolean",
BooleanInputControl::decode, BooleanInputControl::encode);
public static final InputControlType<NumberRangeInputControl> NUMBER_RANGE = define("number_range",
NumberRangeInputControl::decode, NumberRangeInputControl::encode);
public static final InputControlType<SingleOptionInputControl> SINGLE_OPTION = define("single_option",
SingleOptionInputControl::decode, SingleOptionInputControl::encode);
public static final InputControlType<TextInputControl> TEXT = define("text",
TextInputControl::decode, TextInputControl::encode);
static {
REGISTRY.unloadMappings();
}
}
@@ -0,0 +1,146 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTFloat;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTNumber;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class NumberRangeInputControl implements InputControl {
private final int width;
private final Component label;
private final String labelFormat;
private final RangeInfo rangeInfo;
public NumberRangeInputControl(int width, Component label, String labelFormat, RangeInfo rangeInfo) {
this.width = width;
this.label = label;
this.labelFormat = labelFormat;
this.rangeInfo = rangeInfo;
}
public static NumberRangeInputControl decode(NBTCompound compound, PacketWrapper<?> wrapper) {
int width = compound.getNumberTagValueOrDefault("width", 200).intValue();
Component label = compound.getOrThrow("label", AdventureSerializer.serializer(wrapper), wrapper);
String labelFormat = compound.getStringTagValueOrDefault("label_format", "options.generic_value");
RangeInfo rangeInfo = RangeInfo.decode(compound, wrapper);
return new NumberRangeInputControl(width, label, labelFormat, rangeInfo);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, NumberRangeInputControl control) {
if (control.width != 200) {
compound.setTag("width", new NBTInt(control.width));
}
compound.set("label", control.label, AdventureSerializer.serializer(wrapper), wrapper);
if (!"options.generic_value".equals(control.labelFormat)) {
compound.setTag("label_format", new NBTString(control.labelFormat));
}
RangeInfo.encode(compound, wrapper, control.rangeInfo);
}
@Override
public InputControlType<?> getType() {
return InputControlTypes.NUMBER_RANGE;
}
public int getWidth() {
return this.width;
}
public Component getLabel() {
return this.label;
}
public String getLabelFormat() {
return this.labelFormat;
}
public RangeInfo getRangeInfo() {
return this.rangeInfo;
}
public static final class RangeInfo {
private final float start;
private final float end;
private final @Nullable Float initial;
private final @Nullable Float step;
public RangeInfo(float start, float end, @Nullable Float initial, @Nullable Float step) {
if (initial != null) {
float min = Math.min(start, end);
float max = Math.max(start, end);
if (initial < min || initial > max) {
throw new IllegalArgumentException("Initial value " + initial
+ " is outside of range [" + min + ", " + max + "]");
}
}
this.start = start;
this.end = end;
this.initial = initial;
this.step = step;
}
public static RangeInfo decode(NBTCompound compound, PacketWrapper<?> wrapper) {
float start = compound.getNumberTagValueOrThrow("start").floatValue();
float end = compound.getNumberTagValueOrThrow("end").floatValue();
NBTNumber initialTag = compound.getNumberTagOrNull("initial");
Float initial = initialTag != null ? initialTag.getAsFloat() : null;
NBTNumber stepTag = compound.getNumberTagOrNull("step");
Float step = stepTag != null ? stepTag.getAsFloat() : null;
return new RangeInfo(start, end, initial, step);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, RangeInfo rangeInfo) {
compound.setTag("start", new NBTFloat(rangeInfo.start));
compound.setTag("end", new NBTFloat(rangeInfo.end));
if (rangeInfo.initial != null) {
compound.setTag("initial", new NBTFloat(rangeInfo.initial));
}
if (rangeInfo.step != null) {
compound.setTag("step", new NBTFloat(rangeInfo.step));
}
}
public float getStart() {
return this.start;
}
public float getEnd() {
return this.end;
}
public @Nullable Float getInitial() {
return this.initial;
}
public @Nullable Float getStep() {
return this.step;
}
}
}
@@ -0,0 +1,148 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTByte;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.List;
@NullMarked
public class SingleOptionInputControl implements InputControl {
private final int width;
private final List<Entry> options;
private final Component label;
private final boolean labelVisible;
public SingleOptionInputControl(int width, List<Entry> options, Component label, boolean labelVisible) {
boolean initial = false;
for (Entry entry : options) {
if (entry.initial) {
if (initial) {
throw new IllegalArgumentException("Multiple initial values");
}
initial = true;
}
}
this.width = width;
this.options = options;
this.label = label;
this.labelVisible = labelVisible;
}
public static SingleOptionInputControl decode(NBTCompound compound, PacketWrapper<?> wrapper) {
int width = compound.getNumberTagValueOrDefault("width", 200).intValue();
List<Entry> options = compound.getListOrThrow("options", Entry::decode, wrapper);
Component label = compound.getOrThrow("label", AdventureSerializer.serializer(wrapper), wrapper);
boolean labelVisible = compound.getBooleanOr("label_visible", true);
return new SingleOptionInputControl(width, options, label, labelVisible);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, SingleOptionInputControl control) {
if (control.width != 200) {
compound.setTag("width", new NBTInt(control.width));
}
compound.setList("options", control.options, Entry::encode, wrapper);
compound.set("label", control.label, AdventureSerializer.serializer(wrapper), wrapper);
if (!control.labelVisible) {
compound.setTag("label_visible", new NBTByte(false));
}
}
@Override
public InputControlType<?> getType() {
return InputControlTypes.SINGLE_OPTION;
}
public int getWidth() {
return this.width;
}
public List<Entry> getOptions() {
return this.options;
}
public Component getLabel() {
return this.label;
}
public boolean isLabelVisible() {
return this.labelVisible;
}
public static final class Entry {
private final String id;
private final @Nullable Component display;
private final boolean initial;
public Entry(String id, @Nullable Component display, boolean initial) {
this.id = id;
this.display = display;
this.initial = initial;
}
public static Entry decode(NBT nbt, PacketWrapper<?> wrapper) {
if (nbt instanceof NBTString) {
return new Entry(((NBTString) nbt).getValue(), null, false);
}
NBTCompound compound = (NBTCompound) nbt;
String id = compound.getStringTagValueOrThrow("id");
Component display = compound.getOrNull("display", AdventureSerializer.serializer(wrapper), wrapper);
boolean initial = compound.getBooleanOr("initial", false);
return new Entry(id, display, initial);
}
public static NBT encode(PacketWrapper<?> wrapper, Entry entry) {
if (entry.display == null && !entry.initial) {
return new NBTString(entry.id);
}
NBTCompound compound = new NBTCompound();
compound.setTag("id", new NBTString(entry.id));
if (entry.display != null) {
compound.set("display", entry.display, AdventureSerializer.serializer(wrapper), wrapper);
}
if (entry.initial) {
compound.setTag("initial", new NBTByte(true));
}
return compound;
}
public String getId() {
return this.id;
}
public @Nullable Component getDisplay() {
return this.display;
}
public boolean isInitial() {
return this.initial;
}
}
}
@@ -0,0 +1,57 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.util.NbtMapDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtMapEncoder;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class StaticInputControlType<T extends InputControl> extends AbstractMappedEntity implements InputControlType<T> {
private final NbtMapDecoder<T> decoder;
private final NbtMapEncoder<T> encoder;
@ApiStatus.Internal
public StaticInputControlType(
@Nullable TypesBuilderData data,
NbtMapDecoder<T> decoder,
NbtMapEncoder<T> encoder
) {
super(data);
this.decoder = decoder;
this.encoder = encoder;
}
@Override
public T decode(NBTCompound compound, PacketWrapper<?> wrapper) {
return this.decoder.decode(compound, wrapper);
}
@Override
public void encode(NBTCompound compound, PacketWrapper<?> wrapper, T control) {
this.encoder.encode(compound, wrapper, control);
}
}
@@ -0,0 +1,151 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.dialog.input;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTByte;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class TextInputControl implements InputControl {
private final int width;
private final Component label;
private final boolean labelVisible;
private final String initial;
private final int maxLength;
private final @Nullable MultilineOptions multiline;
public TextInputControl(int width, Component label, boolean labelVisible, String initial, int maxLength, @Nullable MultilineOptions multiline) {
if (initial.length() > maxLength) {
throw new IllegalArgumentException("Default text length exceeds allowed size");
}
this.width = width;
this.label = label;
this.labelVisible = labelVisible;
this.initial = initial;
this.maxLength = maxLength;
this.multiline = multiline;
}
public static TextInputControl decode(NBTCompound compound, PacketWrapper<?> wrapper) {
int width = compound.getNumberTagValueOrDefault("width", 200).intValue();
Component label = compound.getOrThrow("label", AdventureSerializer.serializer(wrapper), wrapper);
boolean labelVisible = compound.getBooleanOr("label_visible", true);
String initial = compound.getStringTagValueOrDefault("initial", "");
int maxLength = compound.getNumberTagValueOrDefault("max_length", 32).intValue();
MultilineOptions multiline = compound.getOrNull("multiline", MultilineOptions::decode, wrapper);
return new TextInputControl(width, label, labelVisible, initial, maxLength, multiline);
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, TextInputControl control) {
if (control.width != 200) {
compound.setTag("width", new NBTInt(control.width));
}
compound.set("label", control.label, AdventureSerializer.serializer(wrapper), wrapper);
if (!control.labelVisible) {
compound.setTag("label_visible", new NBTByte(false));
}
if (!control.initial.isEmpty()) {
compound.setTag("initial", new NBTString(control.initial));
}
if (control.maxLength != 32) {
compound.setTag("max_length", new NBTInt(control.maxLength));
}
if (control.multiline != null) {
compound.set("multiline", control.multiline, MultilineOptions::encode, wrapper);
}
}
@Override
public InputControlType<?> getType() {
return InputControlTypes.TEXT;
}
public int getWidth() {
return this.width;
}
public Component getLabel() {
return this.label;
}
public boolean isLabelVisible() {
return this.labelVisible;
}
public String getInitial() {
return this.initial;
}
public int getMaxLength() {
return this.maxLength;
}
public @Nullable MultilineOptions getMultiline() {
return this.multiline;
}
public static final class MultilineOptions {
private final @Nullable Integer maxLines;
private final @Nullable Integer height;
public MultilineOptions(@Nullable Integer maxLines, @Nullable Integer height) {
this.maxLines = maxLines;
this.height = height;
}
public static MultilineOptions decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
Number maxLines = compound.getNumberTagValueOrNull("max_lines");
Number height = compound.getNumberTagValueOrNull("height");
return new MultilineOptions(
maxLines != null ? maxLines.intValue() : null,
height != null ? height.intValue() : null
);
}
public static NBT encode(PacketWrapper<?> wrapper, MultilineOptions options) {
NBTCompound compound = new NBTCompound();
if (options.maxLines != null) {
compound.setTag("max_lines", new NBTInt(options.maxLines));
}
if (options.height != null) {
compound.setTag("height", new NBTInt(options.height));
}
return compound;
}
public @Nullable Integer getMaxLines() {
return this.maxLines;
}
public @Nullable Integer getHeight() {
return this.height;
}
}
}
@@ -296,6 +296,11 @@ public final class EntityTypes {
*/
public static final EntityType LINGERING_POTION = define("lingering_potion", POTION);
/**
* Added with 1.21.6
*/
public static final EntityType HAPPY_GHAST = define("happy_ghast", ABSTRACT_ANIMAL);
/**
* Returns an immutable view of the entity types.
*
@@ -87,14 +87,19 @@ public interface WolfVariant extends MappedEntity, CopyableEntity<WolfVariant>,
MappedEntitySet.write(wrapper, variant.getBiomes());
}
@Deprecated
static WolfVariant decode(NBT nbt, ClientVersion version, @Nullable TypesBuilderData data) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), data);
}
static WolfVariant decode(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
if (version.isOlderThan(ClientVersion.V_1_21_5)) {
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_21_5)) {
ResourceLocation wildTexture = new ResourceLocation(compound.getStringTagValueOrThrow("wild_texture"));
ResourceLocation tameTexture = new ResourceLocation(compound.getStringTagValueOrThrow("tame_texture"));
ResourceLocation angryTexture = new ResourceLocation(compound.getStringTagValueOrThrow("angry_texture"));
MappedEntitySet<Biome> biomes = MappedEntitySet.decode(
compound.getTagOrThrow("biomes"), version, Biomes.getRegistry());
MappedEntitySet<Biome> biomes = compound.getOrThrow("biomes", (tag, ew) ->
MappedEntitySet.decode(tag, ew, Biomes.getRegistry()), wrapper);
return new StaticWolfVariant(data, wildTexture, tameTexture, angryTexture, biomes);
}
NBTCompound assets = compound.getCompoundTagOrThrow("assets");
@@ -105,13 +110,18 @@ public interface WolfVariant extends MappedEntity, CopyableEntity<WolfVariant>,
angryTexture, MappedEntitySet.createEmpty());
}
@Deprecated
static NBT encode(WolfVariant variant, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), variant);
}
static NBT encode(PacketWrapper<?> wrapper, WolfVariant variant) {
NBTCompound compound = new NBTCompound();
if (version.isOlderThan(ClientVersion.V_1_21_5)) {
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_21_5)) {
compound.setTag("wild_texture", new NBTString(variant.getWildTexture().toString()));
compound.setTag("tame_texture", new NBTString(variant.getTameTexture().toString()));
compound.setTag("angry_texture", new NBTString(variant.getAngryTexture().toString()));
compound.setTag("biomes", MappedEntitySet.encode(variant.getBiomes(), version));
compound.set("biomes", variant.getBiomes(), MappedEntitySet::encode, wrapper);
} else {
NBTCompound assets = new NBTCompound();
assets.setTag("wild", new NBTString(variant.getWildTexture().toString()));
@@ -107,6 +107,11 @@ public class ItemStack {
this.registryHolder = registryHolder;
}
public static ItemStack decode(NBT nbt, PacketWrapper<?> wrapper) {
return decode(nbt, wrapper.getServerVersion().toClientVersion());
}
@Deprecated
public static ItemStack decode(NBT nbt, ClientVersion version) {
if (nbt instanceof NBTString) {
ResourceLocation itemName = new ResourceLocation(((NBTString) nbt).getValue());
@@ -131,6 +136,11 @@ public class ItemStack {
return builder.build();
}
public static NBT encode(PacketWrapper<?> wrapper, ItemStack itemStack) {
return encodeForParticle(itemStack, wrapper.getServerVersion().toClientVersion());
}
@Deprecated
public static NBT encodeForParticle(ItemStack itemStack, ClientVersion version) {
if (version.isNewerThanOrEquals(ClientVersion.V_1_20_5)) {
boolean simple = itemStack.isEmpty()
@@ -69,7 +69,7 @@ public final class ItemStackSerialization {
ClientVersion version = wrapper.getServerVersion().toClientVersion();
ItemType type = ItemTypes.getRegistry().getByIdOrThrow(version, typeId);
int amount = wrapper.readByte();
int legacyData = v1_13_2 ? -1 : wrapper.readShort();
int legacyData = version.isOlderThan(ClientVersion.V_1_13) ? wrapper.readShort() : -1;
NBTCompound nbt = wrapper.readNBT();
return ItemStack.builder().type(type).amount(amount)
.nbt(nbt).legacyData(legacyData)
@@ -80,12 +80,14 @@ public final class ItemStackSerialization {
* Removed with 1.20.5
*/
private static void writeLegacy(PacketWrapper<?> wrapper, ItemStack stack) {
if (wrapper.getServerVersion().isOlderThanOrEquals(ServerVersion.V_1_12_2)) {
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_13_2)) {
int typeId = stack.isEmpty() ? -1 : stack.getType().getId(wrapper.getServerVersion().toClientVersion());
wrapper.writeShort(typeId);
if (typeId != -1) {
wrapper.writeByte(stack.getAmount());
wrapper.writeShort(stack.getLegacyData());
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_13)) {
wrapper.writeShort(stack.getLegacyData());
}
wrapper.writeNBT(stack.getNBT());
}
} else if (stack.isEmpty()) {
@@ -22,6 +22,7 @@ import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import java.util.Objects;
@@ -35,14 +36,24 @@ public final class EnchantmentCost {
this.perLevelAboveFirst = perLevelAboveFirst;
}
@Deprecated
public static EnchantmentCost decode(NBT nbt, ClientVersion version) {
return decode(nbt, PacketWrapper.createDummyWrapper(version));
}
public static EnchantmentCost decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
int base = compound.getNumberTagOrThrow("base").getAsInt();
int perLevelAboveFirst = compound.getNumberTagOrThrow("per_level_above_first").getAsInt();
return new EnchantmentCost(base, perLevelAboveFirst);
}
@Deprecated
public static NBT encode(EnchantmentCost cost, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), cost);
}
public static NBT encode(PacketWrapper<?> wrapper, EnchantmentCost cost) {
NBTCompound compound = new NBTCompound();
compound.setTag("base", new NBTInt(cost.base));
compound.setTag("per_level_above_first", new NBTInt(cost.perLevelAboveFirst));
@@ -28,6 +28,8 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.nbt.NBTList;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
import java.util.ArrayList;
import java.util.Collections;
@@ -37,6 +39,7 @@ import java.util.Optional;
import static com.github.retrooper.packetevents.util.adventure.AdventureIndexUtil.indexValueOrThrow;
@NullMarked
public final class EnchantmentDefinition {
private final MappedEntitySet<ItemType> supportedItems;
@@ -65,16 +68,21 @@ public final class EnchantmentDefinition {
this.slots = slots;
}
@Deprecated
public static EnchantmentDefinition decode(NBT nbt, ClientVersion version) {
return decode(nbt, PacketWrapper.createDummyWrapper(version));
}
public static EnchantmentDefinition decode(NBT nbt, PacketWrapper<?> wrapper) {
NBTCompound compound = (NBTCompound) nbt;
MappedEntitySet<ItemType> supportedItems = MappedEntitySet.decode(
compound.getTagOrThrow("supported_items"), version, ItemTypes.getRegistry());
Optional<MappedEntitySet<ItemType>> primaryItems = Optional.ofNullable(compound.getTagOrNull("primary_items"))
.map(items -> MappedEntitySet.decode(items, version, ItemTypes.getRegistry()));
MappedEntitySet<ItemType> supportedItems = compound.getOrThrow("supported_items", (tag, ew) ->
MappedEntitySet.decode(tag, ew, ItemTypes.getRegistry()), wrapper);
Optional<MappedEntitySet<ItemType>> primaryItems = Optional.ofNullable(compound.getOrNull("primary_items",
(tag, ew) -> MappedEntitySet.decode(tag, ew, ItemTypes.getRegistry()), wrapper));
int weight = compound.getNumberTagOrThrow("weight").getAsInt();
int maxLevel = compound.getNumberTagOrThrow("max_level").getAsInt();
EnchantmentCost minCost = EnchantmentCost.decode(compound.getTagOrThrow("min_cost"), version);
EnchantmentCost maxCost = EnchantmentCost.decode(compound.getTagOrThrow("max_cost"), version);
EnchantmentCost minCost = compound.getOrThrow("min_cost", EnchantmentCost::decode, wrapper);
EnchantmentCost maxCost = compound.getOrThrow("max_cost", EnchantmentCost::decode, wrapper);
int anvilCost = compound.getNumberTagOrThrow("anvil_cost").getAsInt();
NBT slotsTag = compound.getTagOrThrow("slots");
@@ -96,24 +104,32 @@ public final class EnchantmentDefinition {
maxLevel, minCost, maxCost, anvilCost, slots);
}
@Deprecated
public static NBT encode(EnchantmentDefinition definition, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), definition);
}
public static NBT encode(PacketWrapper<?> wrapper, EnchantmentDefinition definition) {
NBTCompound compound = new NBTCompound();
encode(compound, wrapper, definition);
return compound;
}
public static void encode(NBTCompound compound, PacketWrapper<?> wrapper, EnchantmentDefinition definition) {
NBTList<NBTString> slotsTag = NBTList.createStringList();
for (EquipmentSlotGroup slot : definition.slots) {
slotsTag.addTag(new NBTString(slot.getId()));
}
NBTCompound compound = new NBTCompound();
compound.setTag("supported_items", MappedEntitySet.encode(
definition.supportedItems, version));
definition.primaryItems.ifPresent(set -> compound.setTag(
"primary_items", MappedEntitySet.encode(set, version)));
compound.set("supported_items", definition.supportedItems, MappedEntitySet::encode, wrapper);
definition.primaryItems.ifPresent(set -> compound.set(
"primary_items", set, MappedEntitySet::encode, wrapper));
compound.setTag("weight", new NBTInt(definition.weight));
compound.setTag("max_level", new NBTInt(definition.maxLevel));
compound.setTag("min_cost", EnchantmentCost.encode(definition.minCost, version));
compound.setTag("max_cost", EnchantmentCost.encode(definition.maxCost, version));
compound.set("min_cost", definition.minCost, EnchantmentCost::encode, wrapper);
compound.set("max_cost", definition.maxCost, EnchantmentCost::encode, wrapper);
compound.setTag("anvil_cost", new NBTInt(definition.anvilCost));
compound.setTag("slots", slotsTag);
return compound;
}
public MappedEntitySet<ItemType> getSupportedItems() {
@@ -25,17 +25,20 @@ import com.github.retrooper.packetevents.protocol.item.enchantment.EnchantmentDe
import com.github.retrooper.packetevents.protocol.mapper.CopyableEntity;
import com.github.retrooper.packetevents.protocol.mapper.DeepComparableEntity;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntity;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntityRefSet;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntitySet;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Optional;
@NullMarked
public interface EnchantmentType extends MappedEntity, CopyableEntity<EnchantmentType>, DeepComparableEntity {
Component getDescription();
@@ -44,30 +47,43 @@ public interface EnchantmentType extends MappedEntity, CopyableEntity<Enchantmen
MappedEntitySet<EnchantmentType> getExclusiveSet();
MappedEntityRefSet<EnchantmentType> getExclusiveRefSet();
StaticComponentMap getEffects();
@Deprecated
static EnchantmentType decode(NBT nbt, ClientVersion version, @Nullable TypesBuilderData data) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), data);
}
static EnchantmentType decode(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
Component description = AdventureSerializer.serializer(version).fromNbtTag(compound.getTagOrThrow("description"));
EnchantmentDefinition definition = EnchantmentDefinition.decode(compound, version);
MappedEntitySet<EnchantmentType> exclusiveSet = Optional.ofNullable(compound.getTagOrNull("exclusive_set"))
.map(tag -> MappedEntitySet.decode(tag, version, EnchantmentTypes.getRegistry()))
Component description = compound.getOrThrow("description", wrapper.getSerializers(), wrapper);
EnchantmentDefinition definition = EnchantmentDefinition.decode(compound, wrapper);
MappedEntityRefSet<EnchantmentType> exclusiveSet = Optional.ofNullable(compound.getTagOrNull("exclusive_set"))
.map(tag -> MappedEntitySet.<EnchantmentType>decodeRefSet(tag, wrapper))
.orElseGet(MappedEntitySet::createEmpty);
StaticComponentMap effects = Optional.ofNullable(compound.getTagOrNull("effects"))
.map(tag -> IComponentMap.decode(tag, version,
.map(tag -> IComponentMap.decode(tag, wrapper,
EnchantEffectComponentTypes.getRegistry()))
.orElse(StaticComponentMap.EMPTY);
return new StaticEnchantmentType(data, description, definition, exclusiveSet, effects);
}
@Deprecated
static NBT encode(EnchantmentType type, ClientVersion version) {
NBTCompound compound = (NBTCompound) EnchantmentDefinition.encode(type.getDefinition(), version);
compound.setTag("description", AdventureSerializer.serializer(version).asNbtTag(type.getDescription()));
if (!type.getExclusiveSet().isEmpty()) {
compound.setTag("exclusive_set", MappedEntitySet.encode(type.getExclusiveSet(), version));
return encode(type, PacketWrapper.createDummyWrapper(version));
}
static NBT encode(EnchantmentType type, PacketWrapper<?> wrapper) {
NBTCompound compound = new NBTCompound();
EnchantmentDefinition.encode(compound, wrapper, type.getDefinition());
compound.set("description", type.getDescription(), wrapper.getSerializers(), wrapper);
if (!type.getExclusiveRefSet().isEmpty()) {
compound.set("exclusive_set", type.getExclusiveRefSet(), MappedEntitySet::encodeRefSet, wrapper);
}
if (!type.getEffects().isEmpty()) {
compound.setTag("effects", IComponentMap.encode(type.getEffects(), version));
compound.set("effects", type.getEffects(), IComponentMap::encode, wrapper);
}
return compound;
}
@@ -25,6 +25,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.mappings.MappingHelper;
import com.github.retrooper.packetevents.util.mappings.VersionedRegistry;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
@@ -64,12 +65,13 @@ public final class EnchantmentTypes {
@ApiStatus.Internal
public static EnchantmentType define(String key) {
PacketWrapper<?> wrapper = PacketWrapper.createDummyWrapper(ClientVersion.getLatest());
return REGISTRY.define(key, data -> {
NBTCompound dataTag = ENCHANTMENT_DATA.get(data.getName());
if (dataTag == null) {
throw new IllegalArgumentException("Can't define enchantment " + data.getName() + ", no data found");
}
return EnchantmentType.decode(dataTag, ClientVersion.getLatest(), data);
return EnchantmentType.decode(dataTag, wrapper, data);
});
}
@@ -18,22 +18,30 @@
package com.github.retrooper.packetevents.protocol.item.enchantment.type;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.component.StaticComponentMap;
import com.github.retrooper.packetevents.protocol.item.enchantment.EnchantmentDefinition;
import com.github.retrooper.packetevents.protocol.mapper.AbstractMappedEntity;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntityRefSet;
import com.github.retrooper.packetevents.protocol.mapper.MappedEntitySet;
import com.github.retrooper.packetevents.protocol.mapper.ResolvableEntity;
import com.github.retrooper.packetevents.util.mappings.GlobalRegistryHolder;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.Objects;
public class StaticEnchantmentType extends AbstractMappedEntity implements EnchantmentType {
@NullMarked
public class StaticEnchantmentType extends AbstractMappedEntity implements EnchantmentType, ResolvableEntity {
private final Component description;
private final EnchantmentDefinition definition;
private final MappedEntitySet<EnchantmentType> exclusiveSet;
private final MappedEntityRefSet<EnchantmentType> exclusiveSetRef;
private @Nullable MappedEntitySet<EnchantmentType> exclusiveSet;
private final StaticComponentMap effects;
public StaticEnchantmentType(
@@ -50,20 +58,27 @@ public class StaticEnchantmentType extends AbstractMappedEntity implements Encha
@Nullable TypesBuilderData data,
Component description,
EnchantmentDefinition definition,
MappedEntitySet<EnchantmentType> exclusiveSet,
MappedEntityRefSet<EnchantmentType> exclusiveSet,
StaticComponentMap effects
) {
super(data);
this.description = description;
this.definition = definition;
this.exclusiveSet = exclusiveSet;
this.exclusiveSetRef = exclusiveSet;
this.effects = effects;
}
@Override
public void doResolve(PacketWrapper<?> wrapper) {
this.exclusiveSet = this.exclusiveSetRef.resolve(wrapper, EnchantmentTypes.getRegistry());
}
@Override
public EnchantmentType copy(@Nullable TypesBuilderData newData) {
return new StaticEnchantmentType(newData, this.description,
this.definition, this.exclusiveSet, this.effects);
StaticEnchantmentType type = new StaticEnchantmentType(newData, this.description,
this.definition, this.exclusiveSetRef, this.effects);
type.exclusiveSet = this.exclusiveSet;
return type;
}
@Override
@@ -78,9 +93,22 @@ public class StaticEnchantmentType extends AbstractMappedEntity implements Encha
@Override
public MappedEntitySet<EnchantmentType> getExclusiveSet() {
if (this.exclusiveSet == null) {
// this shouldn't need to be called normally
this.exclusiveSet = this.exclusiveSetRef.resolve(
PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(),
GlobalRegistryHolder.INSTANCE,
EnchantmentTypes.getRegistry()
);
}
return this.exclusiveSet;
}
@Override
public MappedEntityRefSet<EnchantmentType> getExclusiveRefSet() {
return this.exclusiveSetRef;
}
@Override
public StaticComponentMap getEffects() {
return this.effects;
@@ -94,17 +122,17 @@ public class StaticEnchantmentType extends AbstractMappedEntity implements Encha
StaticEnchantmentType that = (StaticEnchantmentType) obj;
if (!this.description.equals(that.description)) return false;
if (!this.definition.equals(that.definition)) return false;
if (!this.exclusiveSet.equals(that.exclusiveSet)) return false;
if (!this.exclusiveSetRef.equals(that.exclusiveSetRef)) return false;
return this.effects.equals(that.effects);
}
@Override
public int deepHashCode() {
return Objects.hash(super.hashCode(), this.description, this.definition, this.exclusiveSet, this.effects);
return Objects.hash(super.hashCode(), this.description, this.definition, this.exclusiveSetRef, this.effects);
}
@Override
public String toString() {
return "StaticEnchantmentType{description=" + this.description + ", definition=" + this.definition + ", exclusiveSet=" + this.exclusiveSet + ", effects=" + this.effects + "}";
return "StaticEnchantmentType{description=" + this.description + ", definition=" + this.definition + ", exclusiveSetRef=" + this.exclusiveSetRef + ", effects=" + this.effects + "}";
}
}
@@ -28,7 +28,6 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTFloat;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.sound.Sound;
import com.github.retrooper.packetevents.util.MathUtil;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
@@ -82,21 +81,31 @@ public interface Instrument extends MappedEntity, CopyableEntity<Instrument>, De
}
}
@Deprecated
static Instrument decode(NBT nbt, ClientVersion version, @Nullable TypesBuilderData data) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), data);
}
static Instrument decode(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
Sound sound = Sound.decode(compound.getTagOrThrow("sound_event"), version);
Sound sound = compound.getOrThrow("sound_event", Sound::decode, wrapper);
float useSeconds = compound.getNumberTagOrThrow("use_duration").getAsFloat();
float range = compound.getNumberTagOrThrow("range").getAsFloat();
Component description = AdventureSerializer.serializer(version).fromNbtTag(compound.getTagOrThrow("description"));
Component description = compound.getOrThrow("description", wrapper.getSerializers(), wrapper);
return new StaticInstrument(data, sound, useSeconds, range, description);
}
@Deprecated
static NBT encode(Instrument instrument, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), instrument);
}
static NBT encode(PacketWrapper<?> wrapper, Instrument instrument) {
NBTCompound compound = new NBTCompound();
compound.setTag("sound_event", Sound.encode(instrument.getSound(), version));
compound.set("sound_event", instrument.getSound(), Sound::encode, wrapper);
compound.setTag("use_duration", new NBTFloat(instrument.getUseSeconds()));
compound.setTag("range", new NBTFloat(instrument.getRange()));
compound.setTag("description", AdventureSerializer.serializer(version).asNbtTag(instrument.getDescription()));
compound.set("description", instrument.getDescription(), wrapper.getSerializers(), wrapper);
return compound;
}
}
@@ -27,7 +27,6 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTFloat;
import com.github.retrooper.packetevents.protocol.nbt.NBTInt;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.sound.Sound;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
@@ -43,23 +42,31 @@ public interface IJukeboxSong extends MappedEntity, CopyableEntity<IJukeboxSong>
int getComparatorOutput();
@Deprecated
static IJukeboxSong decode(NBT nbt, ClientVersion version, @Nullable TypesBuilderData data) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), data);
}
static IJukeboxSong decode(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
Sound sound = Sound.decode(compound.getTagOrThrow("sound_event"), version);
Component description = AdventureSerializer.serializer(version).fromNbtTag(compound.getTagOrThrow("description"));
Sound sound = compound.getOrThrow("sound_event", Sound::decode, wrapper);
Component description = compound.getOrThrow("description", wrapper.getSerializers(), wrapper);
float length = compound.getNumberTagOrThrow("length_in_seconds").getAsFloat();
int comparator_output = compound.getNumberTagOrThrow("comparator_output").getAsInt();
return new JukeboxSong(data, sound, description, length, comparator_output);
}
@Deprecated
static NBT encode(IJukeboxSong jukeboxSong, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), jukeboxSong);
}
static NBT encode(PacketWrapper<?> wrapper, IJukeboxSong song) {
NBTCompound compound = new NBTCompound();
compound.setTag("sound_event", Sound.encode(jukeboxSong.getSound(), version));
compound.setTag("description", AdventureSerializer.serializer(version).asNbtTag(jukeboxSong.getDescription()));
compound.setTag("length_in_seconds", new NBTFloat(jukeboxSong.getLengthInSeconds()));
compound.setTag("comparator_output", new NBTInt(jukeboxSong.getComparatorOutput()));
compound.set("sound_event", song.getSound(), Sound::encode, wrapper);
compound.set("description", song.getDescription(), wrapper.getSerializers(), wrapper);
compound.setTag("length_in_seconds", new NBTFloat(song.getLengthInSeconds()));
compound.setTag("comparator_output", new NBTInt(song.getComparatorOutput()));
return compound;
}
@@ -81,6 +81,18 @@ public final class JukeboxSongs {
public static final IJukeboxSong CREATOR_MUSIC_BOX = define("creator_music_box", Sounds.MUSIC_DISC_CREATOR_MUSIC_BOX,
73, 11);
/**
* Added with 1.21.6
*/
public static final IJukeboxSong TEARS = define("tears", Sounds.MUSIC_DISC_TEARS,
175, 10);
/**
* Added with 1.21.7
*/
public static final IJukeboxSong LAVA_CHICKEN = define("lava_chicken", Sounds.MUSIC_DISC_LAVA_CHICKEN,
134, 9);
/**
* Returns an immutable view of the jukebox songs.
*
@@ -31,7 +31,6 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTFloat;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
@@ -102,12 +101,17 @@ public interface TrimMaterial extends MappedEntity, CopyableEntity<TrimMaterial>
wrapper.writeComponent(material.getDescription());
}
@Deprecated
static TrimMaterial decode(NBT nbt, ClientVersion version, @Nullable TypesBuilderData data) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), data);
}
static TrimMaterial decode(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
String assetName = compound.getStringTagValueOrThrow("asset_name");
ItemType ingredient = version.isNewerThanOrEquals(ClientVersion.V_1_21_5)
ItemType ingredient = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5)
? null : ItemTypes.getByName(compound.getStringTagValueOrThrow("ingredient"));
float itemModelIndex = version.isNewerThanOrEquals(ClientVersion.V_1_21_4)
float itemModelIndex = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_4)
? FALLBACK_ITEM_MODEL_INDEX : compound.getNumberTagOrThrow("item_model_index").getAsFloat();
NBTCompound overrideArmorMaterialsTag = compound.getCompoundTagOrNull("override_armor_materials");
Map<ArmorMaterial, String> overrideArmorMaterials;
@@ -121,11 +125,16 @@ public interface TrimMaterial extends MappedEntity, CopyableEntity<TrimMaterial>
} else {
overrideArmorMaterials = Collections.emptyMap();
}
Component description = AdventureSerializer.serializer(version).fromNbtTag(((NBTCompound) nbt).getTagOrThrow("description"));
Component description = ((NBTCompound) nbt).getOrThrow("description", wrapper.getSerializers(), wrapper);
return new StaticTrimMaterial(data, assetName, ingredient, itemModelIndex, overrideArmorMaterials, description);
}
@Deprecated
static NBT encode(TrimMaterial material, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), material);
}
static NBT encode(PacketWrapper<?> wrapper, TrimMaterial material) {
NBTCompound overrideArmorMaterialsTag;
if (!material.getOverrideArmorMaterials().isEmpty()) {
overrideArmorMaterialsTag = new NBTCompound();
@@ -140,16 +149,16 @@ public interface TrimMaterial extends MappedEntity, CopyableEntity<TrimMaterial>
NBTCompound compound = new NBTCompound();
compound.setTag("asset_name", new NBTString(material.getAssetName()));
if (version.isOlderThan(ClientVersion.V_1_21_5)) {
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_21_5)) {
compound.setTag("ingredient", new NBTString(material.getIngredient().getName().toString()));
}
if (version.isOlderThan(ClientVersion.V_1_21_4)) {
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_21_4)) {
compound.setTag("item_model_index", new NBTFloat(material.getItemModelIndex()));
}
if (overrideArmorMaterialsTag != null) {
compound.setTag("override_armor_materials", overrideArmorMaterialsTag);
}
compound.setTag("description", AdventureSerializer.serializer(version).asNbtTag(material.getDescription()));
compound.set("description", material.getDescription(), wrapper.getSerializers(), wrapper);
return compound;
}
}
@@ -30,7 +30,6 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTCompound;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.adventure.AdventureSerializer;
import com.github.retrooper.packetevents.util.mappings.TypesBuilderData;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import net.kyori.adventure.text.Component;
@@ -77,24 +76,34 @@ public interface TrimPattern extends MappedEntity, CopyableEntity<TrimPattern>,
wrapper.writeBoolean(pattern.isDecal());
}
@Deprecated
static TrimPattern decode(NBT nbt, ClientVersion version, @Nullable TypesBuilderData data) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), data);
}
static TrimPattern decode(NBT nbt, PacketWrapper<?> wrapper, @Nullable TypesBuilderData data) {
NBTCompound compound = (NBTCompound) nbt;
ResourceLocation assetId = new ResourceLocation(compound.getStringTagValueOrThrow("asset_id"));
ItemType templateItem = version.isNewerThanOrEquals(ClientVersion.V_1_21_5) ? null :
ItemType templateItem = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_21_5) ? null :
ItemTypes.getByName(compound.getStringTagValueOrThrow("template_item"));
Component description = AdventureSerializer.serializer(version).fromNbtTag(compound.getTagOrThrow("description"));
boolean decal = version.isNewerThanOrEquals(ClientVersion.V_1_20_2) && compound.getBoolean("decal");
Component description = compound.getOrThrow("description", wrapper.getSerializers(), wrapper);
boolean decal = wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2) && compound.getBoolean("decal");
return new StaticTrimPattern(data, assetId, templateItem, description, decal);
}
@Deprecated
static NBT encode(TrimPattern pattern, ClientVersion version) {
return encode(PacketWrapper.createDummyWrapper(version), pattern);
}
static NBT encode(PacketWrapper<?> wrapper, TrimPattern pattern) {
NBTCompound compound = new NBTCompound();
compound.setTag("asset_id", new NBTString(pattern.getAssetId().toString()));
if (version.isOlderThan(ClientVersion.V_1_21_5)) {
if (wrapper.getServerVersion().isOlderThan(ServerVersion.V_1_21_5)) {
compound.setTag("template_item", new NBTString(pattern.getTemplateItem().getName().toString()));
}
compound.setTag("description", AdventureSerializer.serializer(version).asNbtTag(pattern.getDescription()));
if (version.isNewerThanOrEquals(ClientVersion.V_1_20_2)) {
compound.set("description", pattern.getDescription(), wrapper.getSerializers(), wrapper);
if (wrapper.getServerVersion().isNewerThanOrEquals(ServerVersion.V_1_20_2)) {
compound.setTag("decal", new NBTByte(pattern.isDecal()));
}
return compound;
@@ -1476,6 +1476,88 @@ public final class ItemTypes {
public static final ItemType BLUE_EGG = builder("blue_egg").setMaxAmount(16).build();
public static final ItemType BROWN_EGG = builder("brown_egg").setMaxAmount(16).build();
/**
* Added with 1.21.6
*/
public static final ItemType DRIED_GHAST = builder("dried_ghast").setMaxAmount(64).setPlacedType(StateTypes.DRIED_GHAST).build();
/**
* Added with 1.21.6
*/
public static final ItemType WHITE_HARNESS = builder("white_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType ORANGE_HARNESS = builder("orange_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType MAGENTA_HARNESS = builder("magenta_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType LIGHT_BLUE_HARNESS = builder("light_blue_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType YELLOW_HARNESS = builder("yellow_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType LIME_HARNESS = builder("lime_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType PINK_HARNESS = builder("pink_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType GRAY_HARNESS = builder("gray_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType LIGHT_GRAY_HARNESS = builder("light_gray_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType CYAN_HARNESS = builder("cyan_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType PURPLE_HARNESS = builder("purple_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType BLUE_HARNESS = builder("blue_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType BROWN_HARNESS = builder("brown_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType GREEN_HARNESS = builder("green_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType RED_HARNESS = builder("red_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType BLACK_HARNESS = builder("black_harness").setMaxAmount(1).build();
/**
* Added with 1.21.6
*/
public static final ItemType HAPPY_GHAST_SPAWN_EGG = builder("happy_ghast_spawn_egg").setMaxAmount(64).build();
/**
* Added with 1.21.6
*/
public static final ItemType MUSIC_DISC_TEARS = builder("music_disc_tears").setMaxAmount(1).setAttributes(ItemAttribute.MUSIC_DISC).build();
/**
* Added with 1.21.7
*/
public static final ItemType MUSIC_DISC_LAVA_CHICKEN = builder("music_disc_lava_chicken").setMaxAmount(1).setAttributes(ItemAttribute.MUSIC_DISC).build();
/**
* @deprecated Burning furnace shows up as a missing texture, removed in 1.9
*/
@@ -1603,7 +1685,8 @@ public final class ItemTypes {
// all versions where base components were changed TODO UPDATE
ClientVersion[] versions = new ClientVersion[]{
ClientVersion.V_1_20_5, ClientVersion.V_1_21, ClientVersion.V_1_21_2,
ClientVersion.V_1_21_4, ClientVersion.V_1_21_5,
ClientVersion.V_1_21_4, ClientVersion.V_1_21_5, ClientVersion.V_1_21_6,
ClientVersion.V_1_21_7,
};
for (ClientVersion version : versions) {
parseAllComponents(version);
@@ -0,0 +1,117 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.mapper;
import com.github.retrooper.packetevents.protocol.nbt.NBT;
import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.util.NbtDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtEncoder;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.mappings.IRegistry;
import com.github.retrooper.packetevents.util.mappings.IRegistryHolder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.util.function.Supplier;
@NullMarked
public interface MappedEntityRef<T extends MappedEntity> extends Supplier<T> {
T get();
static <T extends MappedEntity> MappedEntityRef<T> decode(
NBT tag, IRegistry<T> registry, NbtDecoder<T> decoder, PacketWrapper<?> wrapper
) {
if (tag instanceof NBTString) {
ResourceLocation name = new ResourceLocation(((NBTString) tag).getValue());
return new Named<>(wrapper, registry, name);
}
return new Static<>(decoder.decode(tag, wrapper));
}
static <T extends MappedEntity> NBT encode(PacketWrapper<?> wrapper, NbtEncoder<T> encoder, MappedEntityRef<T> ref) {
if (ref instanceof Named) {
return new NBTString(((Named<T>) ref).name.toString());
} else if (ref instanceof Static) {
return encoder.encode(wrapper, ((Static<T>) ref).entity);
} else {
throw new UnsupportedOperationException("Unsupported MappedEntityRef implementation: " + ref);
}
}
final class Static<T extends MappedEntity> implements MappedEntityRef<T> {
private final T entity;
public Static(T entity) {
this.entity = entity;
}
@Override
public T get() {
return this.entity;
}
}
final class Named<T extends MappedEntity> implements MappedEntityRef<T> {
private final WeakReference<IRegistryHolder> registryHolder;
private final ClientVersion version;
private final IRegistry<T> registry;
private final ResourceLocation name;
private volatile @Nullable T entity;
public Named(PacketWrapper<?> wrapper, IRegistry<T> registry, ResourceLocation name) {
this(wrapper.getRegistryHolder(), wrapper.getServerVersion().toClientVersion(), registry, name);
}
public Named(IRegistryHolder registryHolder, ClientVersion version, IRegistry<T> registry, ResourceLocation name) {
this.registryHolder = new WeakReference<>(registryHolder);
this.version = version;
this.registry = registry;
this.name = name;
}
@Override
public T get() {
T entity = this.entity;
if (entity == null) {
synchronized (this) {
entity = this.entity;
if (entity == null) {
// registry holder may be a user object, so ensure no user objects get leaked accidentally
IRegistryHolder registryHolder = this.registryHolder.get();
if (registryHolder == null) {
throw new IllegalStateException("Registry holder for " + this.registry
+ "/" + this.version + "/" + this.name + "has disappeared");
}
// replace registry and lookup entity
IRegistry<T> registry = registryHolder.getRegistryOr(this.registry, this.version);
entity = registry.getByNameOrThrow(this.name);
this.entity = entity;
}
}
}
return entity;
}
}
}
@@ -0,0 +1,44 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.mapper;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.mappings.IRegistry;
import com.github.retrooper.packetevents.util.mappings.IRegistryHolder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jspecify.annotations.NullMarked;
@NullMarked
public interface MappedEntityRefSet<T extends MappedEntity> {
default MappedEntitySet<T> resolve(PacketWrapper<?> wrapper, IRegistry<T> registry) {
ClientVersion version = wrapper.getServerVersion().toClientVersion();
IRegistry<T> replacedRegistry = wrapper.getRegistryHolder().getRegistryOr(registry, version);
return this.resolve(version, replacedRegistry);
}
default MappedEntitySet<T> resolve(ClientVersion version, IRegistryHolder registryHolder, IRegistry<T> registry) {
IRegistry<T> replacedRegistry = registryHolder.getRegistryOr(registry, version);
return this.resolve(version, replacedRegistry);
}
MappedEntitySet<T> resolve(ClientVersion version, IRegistry<T> registry);
boolean isEmpty();
}
@@ -24,10 +24,14 @@ import com.github.retrooper.packetevents.protocol.nbt.NBTString;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.resources.ResourceLocation;
import com.github.retrooper.packetevents.util.mappings.IRegistry;
import com.github.retrooper.packetevents.util.mappings.IRegistryHolder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
@@ -35,7 +39,8 @@ import java.util.function.BiFunction;
/**
* Either a key to a specific tag or a list of possible entities.
*/
public class MappedEntitySet<T> {
@NullMarked
public class MappedEntitySet<T extends MappedEntity> implements MappedEntityRefSet<T> {
private final @Nullable ResourceLocation tagKey;
private final @Nullable List<T> entities;
@@ -63,6 +68,27 @@ public class MappedEntitySet<T> {
return new MappedEntitySet<>(new ArrayList<>(0));
}
public static <Z extends MappedEntity> MappedEntityRefSet<Z> readRefSet(PacketWrapper<?> wrapper) {
int count = wrapper.readVarInt() - 1;
if (count == -1) {
return new MappedEntitySet<>(wrapper.readIdentifier());
}
int[] entries = wrapper.readVarIntArrayOfSize(Math.min(count, 65536));
return new IdRefSetImpl<>(entries);
}
public static void writeRefSet(PacketWrapper<?> wrapper, MappedEntityRefSet<?> refSet) {
if (refSet instanceof IdRefSetImpl<?>) {
IdRefSetImpl<?> idRefSet = (IdRefSetImpl<?>) refSet;
wrapper.writeVarInt(idRefSet.entries.length + 1);
wrapper.writeVarIntArrayOfSize(idRefSet.entries);
} else if (refSet instanceof MappedEntitySet<?>) {
write(wrapper, (MappedEntitySet<?>) refSet);
} else {
throw new UnsupportedOperationException("Unsupported mapped entity reference set implementation: " + refSet);
}
}
public static <Z extends MappedEntity> MappedEntitySet<Z> read(
PacketWrapper<?> wrapper, BiFunction<ClientVersion, Integer, Z> getter) {
int count = wrapper.readVarInt() - 1;
@@ -90,8 +116,14 @@ public class MappedEntitySet<T> {
}
}
@Deprecated
public static <Z extends MappedEntity> MappedEntitySet<Z> decode(
NBT nbt, ClientVersion version, IRegistry<Z> registry) {
return decode(nbt, PacketWrapper.createDummyWrapper(version), registry);
}
public static <Z extends MappedEntity> MappedEntitySet<Z> decode(
NBT nbt, PacketWrapper<?> wrapper, IRegistry<Z> registry) {
List<Z> list;
if (nbt instanceof NBTString) {
String singleEntry = ((NBTString) nbt).getValue();
@@ -117,7 +149,12 @@ public class MappedEntitySet<T> {
return new MappedEntitySet<>(list);
}
@Deprecated
public static <Z extends MappedEntity> NBT encode(MappedEntitySet<Z> set, ClientVersion version) {
return encodeRefSet(PacketWrapper.createDummyWrapper(version), set);
}
public static <Z extends MappedEntity> NBT encode(PacketWrapper<?> wrapper, MappedEntitySet<Z> set) {
if (set.tagKey != null) {
return new NBTString("#" + set.tagKey);
}
@@ -130,6 +167,69 @@ public class MappedEntitySet<T> {
return listTag;
}
public static <Z extends MappedEntity> MappedEntityRefSet<Z> decodeRefSet(NBT nbt, PacketWrapper<?> wrapper) {
return decodeRefSet(nbt, wrapper.getServerVersion().toClientVersion());
}
@Deprecated
public static <Z extends MappedEntity> MappedEntityRefSet<Z> decodeRefSet(NBT nbt, ClientVersion version) {
List<String> list;
if (nbt instanceof NBTString) {
String singleEntry = ((NBTString) nbt).getValue();
// check whether this is a tag key or a single-entry list
if (!singleEntry.isEmpty() && singleEntry.charAt(0) == '#') {
String tagName = singleEntry.substring(1);
ResourceLocation tagKey = new ResourceLocation(tagName);
return new MappedEntitySet<>(tagKey);
}
// single entry list
list = Collections.singletonList(singleEntry);
} else {
// assume it's a list
NBTList<?> listTag = (NBTList<?>) nbt;
list = new ArrayList<>(listTag.size());
for (NBT tag : listTag.getTags()) {
list.add(((NBTString) tag).getValue());
}
}
return new NameRefSetImpl<>(list);
}
public static <Z extends MappedEntity> NBT encodeRefSet(PacketWrapper<?> wrapper, MappedEntityRefSet<Z> refSet) {
return encodeRefSet(refSet, wrapper.getServerVersion().toClientVersion());
}
@Deprecated
public static <Z extends MappedEntity> NBT encodeRefSet(MappedEntityRefSet<Z> refSet, ClientVersion version) {
if (refSet instanceof NameRefSetImpl<?>) {
NameRefSetImpl<?> nameRefSet = (NameRefSetImpl<?>) refSet;
NBTList<NBTString> listTag = NBTList.createStringList();
for (String entityName : nameRefSet.entries) {
listTag.addTag(new NBTString(entityName));
}
return listTag;
} else if (refSet instanceof MappedEntitySet<?>) {
return encode((MappedEntitySet<?>) refSet, version);
} else {
throw new UnsupportedOperationException("Unsupported mapped entity reference set implementation: " + refSet);
}
}
@Override
public MappedEntitySet<T> resolve(PacketWrapper<?> wrapper, IRegistry<T> registry) {
return this;
}
@Override
public MappedEntitySet<T> resolve(ClientVersion version, IRegistryHolder registryHolder, IRegistry<T> registry) {
return this;
}
@Override
public MappedEntitySet<T> resolve(ClientVersion version, IRegistry<T> registry) {
return this;
}
public boolean isEmpty() {
return this.entities != null && this.entities.isEmpty();
}
@@ -160,4 +260,84 @@ public class MappedEntitySet<T> {
public String toString() {
return "MappedEntitySet{tagKey=" + this.tagKey + ", entities=" + this.entities + '}';
}
private static final class IdRefSetImpl<T extends MappedEntity> implements MappedEntityRefSet<T> {
private final int[] entries;
public IdRefSetImpl(int[] entries) {
this.entries = entries;
}
@Override
public MappedEntitySet<T> resolve(ClientVersion version, IRegistry<T> registry) {
List<T> entities = new ArrayList<>(this.entries.length);
for (int entityId : this.entries) {
entities.add(registry.getByIdOrThrow(version, entityId));
}
return new MappedEntitySet<>(entities);
}
@Override
public boolean isEmpty() {
return this.entries.length == 0;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof IdRefSetImpl)) return false;
IdRefSetImpl<?> idRefSet = (IdRefSetImpl<?>) obj;
return Arrays.equals(this.entries, idRefSet.entries);
}
@Override
public int hashCode() {
return Arrays.hashCode(this.entries);
}
@Override
public String toString() {
return "IdRefSetImpl{entries=" + Arrays.toString(this.entries) + '}';
}
}
private static final class NameRefSetImpl<T extends MappedEntity> implements MappedEntityRefSet<T> {
private final List<String> entries;
public NameRefSetImpl(List<String> entries) {
this.entries = entries;
}
@Override
public MappedEntitySet<T> resolve(ClientVersion version, IRegistry<T> registry) {
List<T> entities = new ArrayList<>(this.entries.size());
for (String entityName : this.entries) {
entities.add(registry.getByNameOrThrow(entityName));
}
return new MappedEntitySet<>(entities);
}
@Override
public boolean isEmpty() {
return this.entries.isEmpty();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof NameRefSetImpl)) return false;
NameRefSetImpl<?> that = (NameRefSetImpl<?>) obj;
return this.entries.equals(that.entries);
}
@Override
public int hashCode() {
return Objects.hashCode(this.entries);
}
@Override
public String toString() {
return "NameRefSetImpl{entries=" + this.entries + '}';
}
}
}
@@ -0,0 +1,36 @@
/*
* This file is part of packetevents - https://github.com/retrooper/packetevents
* Copyright (C) 2025 retrooper and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.retrooper.packetevents.protocol.mapper;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.util.mappings.IRegistryHolder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Used internally for loading additional data
* after registry entries have already been added.
*/
@ApiStatus.Internal
@NullMarked
public interface ResolvableEntity {
void doResolve(PacketWrapper<?> wrapper);
}
@@ -18,11 +18,17 @@
package com.github.retrooper.packetevents.protocol.nbt;
import com.github.retrooper.packetevents.protocol.util.NbtDecoder;
import com.github.retrooper.packetevents.protocol.util.NbtEncoder;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -108,6 +114,20 @@ public class NBTCompound extends NBT {
return getTagOfTypeOrNull(key, NBTCompound.class);
}
public Number getNumberTagValueOrThrow(String key) {
return this.getNumberTagOrThrow(key).getAsNumber();
}
public @Nullable Number getNumberTagValueOrNull(String key) {
return this.getNumberTagValueOrDefault(key, null);
}
@Contract("_, !null -> !null")
public @Nullable Number getNumberTagValueOrDefault(String key, @Nullable Number number) {
NBTNumber tag = this.getNumberTagOrNull(key);
return tag != null ? tag.getAsNumber() : number;
}
public NBTNumber getNumberTagOrThrow(String key) {
return getTagOfTypeOrThrow(key, NBTNumber.class);
}
@@ -207,9 +227,92 @@ public class NBTCompound extends NBT {
}
public boolean getBoolean(String string) {
return this.getBooleanOr(string, false);
}
public boolean getBooleanOr(String string, boolean defaultValue) {
NBTNumber nbtByte = this.getTagOfTypeOrNull(string, NBTNumber.class);
// Empty byte tags are considered 0
return nbtByte != null && nbtByte.getAsByte() != 0;
return nbtByte != null ? nbtByte.getAsByte() != 0 : defaultValue;
}
@Contract("_, _, !null, _ -> !null")
public <T> @Nullable T getOr(String key, NbtDecoder<T> decoder, @Nullable T def, PacketWrapper<?> wrapper) {
NBT tag = this.getTagOrNull(key);
return tag != null ? decoder.decode(tag, wrapper) : def;
}
public <T> @Nullable T getOrNull(String key, NbtDecoder<T> decoder, PacketWrapper<?> wrapper) {
return this.getOr(key, decoder, null, wrapper);
}
public <T> T getOrThrow(String key, NbtDecoder<T> decoder, PacketWrapper<?> wrapper) {
return decoder.decode(this.getTagOrThrow(key), wrapper);
}
@Contract("_, _, !null, _ -> !null")
public <T> @Nullable List<T> getListOr(String key, NbtDecoder<T> decoder, @Nullable List<T> def, PacketWrapper<?> wrapper) {
NBT tag = this.getTagOrNull(key);
if (tag instanceof NBTList) {
// entries in list format
List<? extends NBT> tags = ((NBTList<?>) tag).getTags();
List<T> list = new ArrayList<>(tags.size());
for (NBT element : tags) {
list.add(decoder.decode(element, wrapper));
}
return list;
} else if (tag != null) {
// single entry
List<T> list = new ArrayList<>(1);
list.add(decoder.decode(tag, wrapper));
return list;
} else {
// null, return default
return def;
}
}
public <T> @Nullable List<T> getListOrNull(String key, NbtDecoder<T> decoder, PacketWrapper<?> wrapper) {
return this.getListOr(key, decoder, null, wrapper);
}
public <T> List<T> getListOrEmpty(String key, NbtDecoder<T> decoder, PacketWrapper<?> wrapper) {
return this.getListOr(key, decoder, Collections.emptyList(), wrapper);
}
public <T> List<T> getListOrThrow(String key, NbtDecoder<T> decoder, PacketWrapper<?> wrapper) {
List<T> list = this.getListOrNull(key, decoder, wrapper);
if (list == null) {
throw new IllegalStateException(MessageFormat.format("NBT {0} does not exist", key));
}
return null;
}
public <T> void set(String key, T value, NbtEncoder<T> encoder, PacketWrapper<?> wrapper) {
this.setTag(key, encoder.encode(wrapper, value));
}
public <T> void setList(String key, List<T> value, NbtEncoder<T> encoder, PacketWrapper<?> wrapper) {
if (value.isEmpty()) {
this.setTag(key, new NBTList<>(NBTType.END, 0));
} else {
// determine list type using first value in list
NBT firstVal = encoder.encode(wrapper, value.get(0));
NBTList<?> list = new NBTList<>(firstVal.getType(), value.size());
list.addTagUnsafe(firstVal);
// add remaining list entries
for (int i = 1; i < value.size(); i++) {
list.addTagUnsafe(encoder.encode(wrapper, value.get(i)));
}
this.setTag(key, list);
}
}
public <T> void setCompactList(String key, List<T> value, NbtEncoder<T> encoder, PacketWrapper<?> wrapper) {
if (value.size() == 1) {
this.set(key, value.get(0), encoder, wrapper);
} else {
this.setList(key, value, encoder, wrapper);
}
}
@Override
Loaded 100 of 215 files, more files were not shown because too many files have changed in this diff. Show more