Compare commits

...
64 Commits
Author SHA1 Message Date
retrooper cdf3521154 packetevents 2.9.3 release 2025-07-17 16:36:51 +02:00
retrooper 820b530240 add 1.21.8 server version constant 2025-07-17 16:11:48 +02:00
retrooper 1823f61748 increment version to 2.9.3-snapshot 2025-07-17 15:54:24 +02:00
retrooper d27d8ffd6a packetevents 2.9.2 release 2025-07-17 15:18:28 +02:00
retrooper 7a57012822 Revert "Cleanup gradle configuration mess"
This reverts commit cfb054b8b2.
2025-07-17 14:01:23 +02:00
retrooper 0a10f4d861 Merge pull request #1265 from Luuzzi/2.0
fix spawn position for versions lower than 1.8 & protocol 4 support
2025-07-13 22:52:41 +02:00
retrooper d1a3145483 Merge pull request #1300 from GrimAnticheat/feature/read-list-max-size
Add new readList(Reader<K> reader, int maxSize) function to PacketWrapper
2025-07-13 22:46:25 +02:00
retrooper 08096106f8 patch: UserLoginEvent always fired in PlayerJoinEvent to provide UUID and username (so it's not a breaking change). 2025-07-13 22:41:22 +02:00
Luuzzi a3fbccc454 1.7.5 and lower doesn't have action START_JUMPING_WITH_HORSE 2025-07-13 20:36:10 +03:00
Luuzzi 9ea931d58d resolve #1301 2025-07-13 20:19:39 +03:00
Alexandr 364be4e34d Merge branch 'retrooper:2.0' into 2.0 2025-07-12 19:11:19 +03:00
Axionize b22394c288 Add new readList(Reader<K> reader, int maxSize) function to PacketWrapper 2025-07-12 09:49:19 -04:00
retrooper 208dfbc3f3 Merge pull request #1294 from booky10/fix/gradle-config-mess
Cleanup gradle configuration mess
2025-07-10 09:11:36 +02:00
booky10 cfb054b8b2 Cleanup gradle configuration mess 2025-07-10 04:58:23 +02:00
booky10 f47e6880fd [skip ci] Add fabric production run task 2025-07-10 02:53:39 +02:00
booky10 f55f791f8a [skip ci] Update gradle wrapper to v8.14.3 2025-07-10 02:53:38 +02:00
booky10 ba389ddb57 [skip ci] Fix changelog 2025-07-10 02:53:38 +02:00
booky10 2ce143d466 [skip ci] Increment version to v2.9.2 snapshot 2025-07-10 02:53:38 +02:00
booky10 b7f99503d9 Release packetevents v2.9.1 2025-07-10 02:14:43 +02:00
booky10 52adfbae8a Fix "publishMods" task name breaking shadow jar exclusions
This should be fixed properly, this is just a quick fix to get out a proper release
2025-07-10 02:08:11 +02:00
booky10 a861066cab Don't copy artifacts to root build directory 2025-07-10 02:07:21 +02:00
retrooper 1acec02bfb PacketEvents 2.9.1 snapshot 2025-07-10 01:27:48 +02:00
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
Alexandr b62fc6922c Merge branch 'retrooper:2.0' into 2.0 2025-06-30 06:55:33 +03:00
Luuzzi c6f129cdd3 added setData and getData to WrappedBlockState 2025-06-28 22:49:57 +03:00
Luuzzi 22b28b7b35 1_7_10 -> 1_7_2. Also wrapper fixes 2025-06-26 23:23:17 +03:00
Luuzzi 9e7de426c0 fix incorrect shifts(get, set) in Chunk_v1_7 2025-06-26 13:17:53 +03:00
Luuzzi a5188e5d78 added support for 1.7.2-1.7.5, seems it works fine 🤔 2025-06-26 07:03:53 +03:00
Luuzzi cce0ac2d7b fix spawn position for versions lower than 1.8 2025-06-26 06:27:49 +03: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 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
80 changed files with 1267 additions and 224 deletions

No files matched your search

+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
+235
View File
@@ -0,0 +1,235 @@
!!!!! v2.9.3
# PacketEvents 2.9.3 is out now! 🎉
This is an update that adds support for Minecraft 1.21.8 server builds.
# 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 support for 1.21.8 Minecraft server builds.
**View Full Changelog**: https://github.com/retrooper/packetevents/compare/v2.9.2...v2.9.3
## 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.9.2
# PacketEvents 2.9.2 is out now! 🎉
This is a patch update that fixes issues with UserLoginEvent (which previously contained null user data) and with importing Adventure serializers.
# 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 support for 1.7.2+ Minecraft protocol (useful for proxies)
* Fixed UserLoginEvent. It is now (always) triggered by PlayerJoinEvent to prevent User instances from containing null data.
* Fixed importing Adventure serializers.
**View Full Changelog**: https://github.com/retrooper/packetevents/compare/v2.9.1...v2.9.2
## 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.9.1
# PacketEvents 2.9.1 is out now! 🎉
This is a hotfix patch update to fix issues with 2.9.0 release assets. See below for the 2.9.0 changelog.
### 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.1
## 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.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
!!!!!
@@ -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.
*/
@@ -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
@@ -32,7 +32,8 @@ import org.jetbrains.annotations.NotNull;
*/
public enum ServerVersion {
//TODO Rename to MinecraftVersion?
V_1_7_10(5),
V_1_7_2(4), V_1_7_4(4), V_1_7_5(4),
V_1_7_6(5), V_1_7_7(5), V_1_7_8(5), V_1_7_9(5), V_1_7_10(5),
V_1_8(47), V_1_8_3(47), V_1_8_8(47),
V_1_9(107), V_1_9_1(108), V_1_9_2(109), V_1_9_4(110),
//1.10 and 1.10.1 are redundant
@@ -50,7 +51,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_6(771), V_1_21_7(772),
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), V_1_21_8(772),
//TODO UPDATE Add server version constant
ERROR(-1, true);
@@ -19,6 +19,8 @@
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;
@@ -26,19 +28,24 @@ import org.jspecify.annotations.NullMarked;
@NullMarked
public class ShowDialogClickEvent implements ClickEvent {
private final Dialog dialog;
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) {
Dialog dialog = compound.getOrThrow("dialog", Dialog::decode, 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.set("dialog", clickEvent.dialog, Dialog::encode, wrapper);
compound.setTag("dialog", MappedEntityRef.encode(wrapper, Dialog::encode, clickEvent.dialog));
}
@Override
@@ -48,10 +55,14 @@ public class ShowDialogClickEvent implements ClickEvent {
@Override
public net.kyori.adventure.text.event.ClickEvent asAdventure() {
return net.kyori.adventure.text.event.ClickEvent.showDialog(this.dialog);
return net.kyori.adventure.text.event.ClickEvent.showDialog(this.dialog.get());
}
public MappedEntityRef<Dialog> getDialogRef() {
return this.dialog;
}
public Dialog getDialog() {
return this.dialog;
return this.dialog.get();
}
}
@@ -19,15 +19,14 @@
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;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
public class ChatMessage {
private Component chatContent;
private ChatType type;
private final GsonComponentSerializer gson = GsonComponentSerializer.gson();
protected ChatMessage(Component chatContent, ChatType type) {
this.chatContent = chatContent;
this.type = type;
@@ -37,16 +36,16 @@ public class ChatMessage {
return chatContent;
}
public String getChatContentJson() {
return gson.serialize(chatContent);
public String getChatContentJson(ClientVersion version) {
return AdventureSerializer.serializer(version).asJson(this.getChatContent());
}
public void setChatContent(Component chatContent) {
this.chatContent = chatContent;
}
public void setChatContentJson(String jsonContent) {
this.chatContent = gson.deserialize(jsonContent);
public void setChatContentJson(ClientVersion version, String json) {
this.setChatContent(AdventureSerializer.serializer(version).fromJson(json));
}
public ChatType getType() {
@@ -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;
}
}
}
@@ -206,7 +206,7 @@ public final class PacketType {
//TODO UPDATE Update packet type mappings (clientbound pt. 1)
private static final VersionMapper CLIENTBOUND_PLAY_VERSION_MAPPER = new VersionMapper(
ClientVersion.V_1_7_10,
ClientVersion.V_1_7_2,
ClientVersion.V_1_8,
ClientVersion.V_1_9,
ClientVersion.V_1_10,
@@ -235,7 +235,7 @@ public final class PacketType {
//TODO UPDATE Update packet type mappings (serverbound pt. 1)
private static final VersionMapper SERVERBOUND_PLAY_VERSION_MAPPER = new VersionMapper(
ClientVersion.V_1_7_10,
ClientVersion.V_1_7_2,
ClientVersion.V_1_8,
ClientVersion.V_1_9,
ClientVersion.V_1_12,
@@ -1075,7 +1075,7 @@ public final class PacketType {
USE_BED(WrapperPlayServerUseBed.class),
SPAWN_WEATHER_ENTITY(WrapperPlayServerSpawnWeatherEntity.class),
TITLE(WrapperPlayServerSetTitleSubtitle.class),
WORLD_BORDER(WrapperPlayServerInitializeWorldBorder.class),
WORLD_BORDER(WrapperPlayServerWorldBorder.class),
COMBAT_EVENT(WrapperPlayServerCombatEvent.class),
ENTITY_MOVEMENT(WrapperPlayServerEntityMovement.class),
SPAWN_LIVING_ENTITY(WrapperPlayServerSpawnLivingEntity.class),
@@ -1125,7 +1125,7 @@ public final class PacketType {
INITIALIZE_WORLD_BORDER(WrapperPlayServerInitializeWorldBorder.class),
KEEP_ALIVE(WrapperPlayServerKeepAlive.class),
CHUNK_DATA(WrapperPlayServerChunkData.class),
EFFECT(null),
EFFECT(WrapperPlayServerEffect.class),
PARTICLE(WrapperPlayServerParticle.class),
UPDATE_LIGHT(WrapperPlayServerUpdateLight.class),
JOIN_GAME(WrapperPlayServerJoinGame.class),
@@ -39,6 +39,7 @@ import java.util.List;
* @since 1.6.9
*/
public enum ClientVersion {
V_1_7_2(4),
V_1_7_10(5),
V_1_8(47),
@@ -111,7 +112,7 @@ public enum ClientVersion {
//TODO UPDATE Add new protocol version field
@Deprecated
LOWER_THAN_SUPPORTED_VERSIONS(V_1_7_10.protocolVersion - 1, true),
LOWER_THAN_SUPPORTED_VERSIONS(V_1_7_2.protocolVersion - 1, true),
//TODO UPDATE Update HIGHER_THAN_SUPPORTED_VERSIONS field
@Deprecated
HIGHER_THAN_SUPPORTED_VERSIONS(V_1_21_7.protocolVersion + 1, true),
@@ -18,11 +18,9 @@
package com.github.retrooper.packetevents.protocol.world.chunk.impl.v1_7;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.world.chunk.BaseChunk;
import com.github.retrooper.packetevents.protocol.world.chunk.ByteArray3d;
import com.github.retrooper.packetevents.protocol.world.chunk.NibbleArray3d;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
public class Chunk_v1_7 implements BaseChunk {
private final ByteArray3d blocks;
@@ -45,13 +43,21 @@ public class Chunk_v1_7 implements BaseChunk {
@Override
public int getBlockId(int x, int y, int z) {
return blocks.get(x, y, z) | (extendedBlocks.get(x, y, z) << 12);
int blockId = (blocks.get(x, y, z) & 0xFF) << 4;
blockId |= metadata.get(x, y, z) & 0xF;
if (extendedBlocks != null) {
blockId |= (extendedBlocks.get(x, y, z) & 0xF) << 8;
}
return blockId;
}
@Override
public void set(int x, int y, int z, int combinedID) {
blocks.set(x, y, z, combinedID & 0xFF);
extendedBlocks.set(x, y, z, combinedID >> 12);
blocks.set(x, y, z, (combinedID >> 4) & 0xFF);
metadata.set(x, y, z, combinedID & 0xF);
if (extendedBlocks != null) {
extendedBlocks.set(x, y, z, (combinedID >> 8) & 0xF);
}
}
@Override
@@ -561,6 +561,16 @@ public class WrappedBlockState {
return type;
}
public Object getData(StateValue stateValue) {
return data.get(stateValue);
}
public void setData(StateValue stateValue, Object object) {
checkIfCloneNeeded();
data.put(stateValue, object);
checkIsStillValid();
}
// Begin all block data types
public int getAge() {
return (int) data.get(StateValue.AGE);
@@ -1362,6 +1362,18 @@ public class PacketWrapper<T extends PacketWrapper<T>> {
public <K, C extends Collection<K>> C readCollection(IntFunction<C> function, Reader<K> reader) {
int size = this.readVarInt();
return _readCollection(function, reader, size);
}
public <K, C extends Collection<K>> C readCollection(IntFunction<C> function, Reader<K> reader, int maxSize) {
int size = this.readVarInt();
if (size > maxSize) {
throw new RuntimeException(size + " elements exceeded max size of: " + maxSize);
}
return _readCollection(function, reader, size);
}
private <K, C extends Collection<K>> C _readCollection(IntFunction<C> function, Reader<K> reader, int size) {
Collection<K> collection = function.apply(size);
for (int i = 0; i < size; ++i) {
collection.add(reader.apply(this));
@@ -1380,6 +1392,10 @@ public class PacketWrapper<T extends PacketWrapper<T>> {
return this.readCollection(ArrayList::new, reader);
}
public <K> List<K> readList(Reader<K> reader, int maxSize) {
return this.readCollection(ArrayList::new, reader, maxSize);
}
public <K> void writeList(List<K> list, Writer<K> writer) {
writeVarInt(list.size());
for (K key : list) {
@@ -76,7 +76,7 @@ public class WrapperCommonClientSettings<T extends WrapperCommonClientSettings<T
this.viewDistance = this.readByte();
this.chatVisibility = this.readEnum(ChatVisibility.values());
this.chatColors = this.readBoolean();
if (this.serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
this.ignoredDifficulty = this.readByte();
if (this.readBoolean()) { // show cape
this.skinMask = SkinSection.CAPE.getMask();
@@ -98,7 +98,7 @@ public class WrapperCommonClientSettings<T extends WrapperCommonClientSettings<T
this.writeByte(this.viewDistance);
this.writeEnum(this.chatVisibility);
this.writeBoolean(this.chatColors);
if (this.serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
this.writeByte(this.ignoredDifficulty);
this.writeBoolean(SkinSection.CAPE.isSet(this.skinMask));
} else {
@@ -64,7 +64,7 @@ public class WrapperPlayClientAnimation extends PacketWrapper<WrapperPlayClientA
/**
* Hand used for the animation.
* On {@link ClientVersion#V_1_9}, an off-hand was introduced and specifies which arm has been swung.
* For {@link ClientVersion#V_1_8} and {@link ClientVersion#V_1_7_10} clients only have a main hand.
* For {@link ClientVersion#V_1_8} and {@link ClientVersion#V_1_7_2} clients only have a main hand.
*
* @return Hand
*/
@@ -75,7 +75,7 @@ public class WrapperPlayClientAnimation extends PacketWrapper<WrapperPlayClientA
/**
* Modify the hand used for the animation.
* On {@link ClientVersion#V_1_9}, an off-hand was introduced and specifies which arm has been swung.
* For {@link ClientVersion#V_1_8} and {@link ClientVersion#V_1_7_10} clients only have a main hand.
* For {@link ClientVersion#V_1_8} and {@link ClientVersion#V_1_7_2} clients only have a main hand.
* Modifying the hand on 1.8 and 1.7 clients is redundant.
*
* @param interactionHand Hand used for the animation
@@ -106,7 +106,8 @@ public class WrapperPlayClientEntityAction extends PacketWrapper<WrapperPlayClie
@Override
public int getId(ServerVersion version) {
return version.isNewerThanOrEquals(ServerVersion.V_1_21_6)
? -1 : this.ordinal();
? -1 : version.isOlderThanOrEquals(ServerVersion.V_1_7_10)
? 1 : this.ordinal();
}
},
/**
@@ -117,7 +118,8 @@ public class WrapperPlayClientEntityAction extends PacketWrapper<WrapperPlayClie
@Override
public int getId(ServerVersion version) {
return version.isNewerThanOrEquals(ServerVersion.V_1_21_6)
? -1 : this.ordinal();
? -1 : version.isOlderThanOrEquals(ServerVersion.V_1_7_10)
? 2 : this.ordinal();
}
},
LEAVE_BED {
@@ -126,7 +128,7 @@ public class WrapperPlayClientEntityAction extends PacketWrapper<WrapperPlayClie
if (version.isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
return this.ordinal() - 2;
}
return this.ordinal();
return version.isOlderThanOrEquals(ServerVersion.V_1_7_10) ? 3 : this.ordinal();
}
},
START_SPRINTING {
@@ -135,7 +137,7 @@ public class WrapperPlayClientEntityAction extends PacketWrapper<WrapperPlayClie
if (version.isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
return this.ordinal() - 2;
}
return this.ordinal();
return version.isOlderThanOrEquals(ServerVersion.V_1_7_10) ? 4 : this.ordinal();
}
},
STOP_SPRINTING {
@@ -144,7 +146,7 @@ public class WrapperPlayClientEntityAction extends PacketWrapper<WrapperPlayClie
if (version.isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
return this.ordinal() - 2;
}
return this.ordinal();
return version.isOlderThanOrEquals(ServerVersion.V_1_7_10) ? 5 : this.ordinal();
}
},
START_JUMPING_WITH_HORSE {
@@ -153,7 +155,8 @@ public class WrapperPlayClientEntityAction extends PacketWrapper<WrapperPlayClie
if (version.isNewerThanOrEquals(ServerVersion.V_1_21_6)) {
return this.ordinal() - 2;
}
return this.ordinal();
return version.isOlderThanOrEquals(ServerVersion.V_1_7_10) ?
(version.isOlderThanOrEquals(ServerVersion.V_1_7_5) ? -1 : 6) : this.ordinal();
}
},
/**
@@ -54,7 +54,7 @@ public class WrapperPlayClientInteractEntity extends PacketWrapper<WrapperPlayCl
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
this.entityID = readInt();
byte typeIndex = readByte();
this.interactAction = InteractAction.VALUES[typeIndex];
@@ -91,7 +91,7 @@ public class WrapperPlayClientInteractEntity extends PacketWrapper<WrapperPlayCl
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(entityID);
writeByte(interactAction.ordinal());
} else {
@@ -86,7 +86,7 @@ public class WrapperPlayClientPlayerBlockPlacement extends PacketWrapper<Wrapper
sequence = readVarInt();
}
} else {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
blockPosition = new Vector3i(readInt(), readUnsignedByte(), readInt());
} else {
blockPosition = readBlockPosition();
@@ -127,7 +127,7 @@ public class WrapperPlayClientPlayerBlockPlacement extends PacketWrapper<Wrapper
writeVarInt(sequence);
}
} else {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(blockPosition.x);
writeByte(blockPosition.y);
writeInt(blockPosition.z);
@@ -72,7 +72,7 @@ public class WrapperPlayClientPlayerFlying extends PacketWrapper<WrapperPlayClie
if (positionChanged) {
double x = readDouble();
double y = readDouble();
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
//Can be ignored, cause stance = (y + 1.62)
double stance = readDouble();
}
@@ -93,7 +93,7 @@ public class WrapperPlayClientPlayerFlying extends PacketWrapper<WrapperPlayClie
public void write() {
if (positionChanged) {
writeDouble(location.getPosition().getX());
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
//Can be ignored, cause stance = (y + 1.62)
writeDouble(location.getPosition().getY() + 1.62);
}
@@ -58,7 +58,7 @@ public class WrapperPlayClientPluginMessage extends PacketWrapper<WrapperPlayCli
this.channelName = readString(20);
}
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
//It is ignored, because we don't need it
int legacyDataSize = readShort();
}
@@ -75,7 +75,7 @@ public class WrapperPlayClientPluginMessage extends PacketWrapper<WrapperPlayCli
else {
writeString(this.channelName, 20);
}
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeShort(this.data.length);
}
writeBytes(this.data);
@@ -45,7 +45,7 @@ public class WrapperPlayServerBlockAction extends PacketWrapper<WrapperPlayServe
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
int x = readInt();
int y = readShort();
int z = readInt();
@@ -60,7 +60,7 @@ public class WrapperPlayServerBlockAction extends PacketWrapper<WrapperPlayServe
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(blockPosition.x);
writeShort(blockPosition.y);
writeInt(blockPosition.z);
@@ -43,7 +43,7 @@ public class WrapperPlayServerBlockBreakAnimation extends PacketWrapper<WrapperP
@Override
public void read() {
entityID = readVarInt();
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
int x = readInt();
int y = readInt();
int z = readInt();
@@ -57,7 +57,7 @@ public class WrapperPlayServerBlockBreakAnimation extends PacketWrapper<WrapperP
@Override
public void write() {
writeVarInt(entityID);
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(blockPosition.x);
writeInt(blockPosition.y);
writeInt(blockPosition.z);
@@ -41,7 +41,7 @@ public class WrapperPlayServerBlockChange extends PacketWrapper<WrapperPlayServe
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
blockPosition = new Vector3i(readInt(), readUnsignedByte(), readInt());
int block = readVarInt();
int blockData = readUnsignedByte();
@@ -54,7 +54,7 @@ public class WrapperPlayServerBlockChange extends PacketWrapper<WrapperPlayServe
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(blockPosition.getX());
writeByte(blockPosition.getY());
writeInt(blockPosition.getZ());
@@ -329,7 +329,8 @@ public class WrapperPlayServerChunkData extends PacketWrapper<WrapperPlayServerC
this.buffer = originalBuffer;
// write the same amount of zero bytes mojang also writes
if (this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_21_5)) {
if (this.serverVersion.isOlderThan(ServerVersion.V_1_21_6)
&& this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_21_5)) {
int zeroBytes = ChunkReader_v1_18.getMojangZeroByteSuffixLength(chunks);
int newWriterIndex = ByteBufHelper.writerIndex(dataBuffer) + zeroBytes;
// allocate enough space for the zeros
@@ -41,7 +41,7 @@ public class WrapperPlayServerCollectItem extends PacketWrapper<WrapperPlayServe
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
collectedEntityId = readInt();
collectorEntityId = readInt();
} else {
@@ -55,7 +55,7 @@ public class WrapperPlayServerCollectItem extends PacketWrapper<WrapperPlayServe
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(collectedEntityId);
writeInt(collectorEntityId);
} else {
@@ -45,7 +45,7 @@ public class WrapperPlayServerDestroyEntities extends PacketWrapper<WrapperPlayS
if (serverVersion == ServerVersion.V_1_17) {
entityIDs = new int[]{readVarInt()};
} else {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
int entityIDCount = readUnsignedByte();
entityIDs = new int[entityIDCount];
for (int i = 0; i < entityIDCount; i++) {
@@ -66,7 +66,7 @@ public class WrapperPlayServerDestroyEntities extends PacketWrapper<WrapperPlayS
if (serverVersion == ServerVersion.V_1_17) {
writeVarInt(entityIDs[0]);
} else {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeByte(entityIDs.length);
for (int entityID : entityIDs) {
writeInt(entityID);
@@ -0,0 +1,114 @@
/*
* 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.wrapper.play.server;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
/**
* Mojang name: ClientboundLevelEventPacket
*/
public class WrapperPlayServerEffect extends PacketWrapper<WrapperPlayServerEffect> {
private int type;
private Vector3i position;
private int data;
private boolean globalEvent;
public WrapperPlayServerEffect(PacketSendEvent type) {
super(type);
}
public WrapperPlayServerEffect(int type, Vector3i position, int data, boolean globalEvent) {
super(PacketType.Play.Server.EFFECT);
this.type = type;
this.position = position;
this.data = data;
this.globalEvent = globalEvent;
}
@Override
public void read() {
this.type = this.readInt();
if (this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_8)) {
this.position = this.readBlockPosition();
} else {
this.position = new Vector3i(this.readInt(), this.readByte() & 0xFF, this.readInt());
}
this.data = this.readInt();
this.globalEvent = this.readBoolean();
}
@Override
public void write() {
this.writeInt(this.type);
if (this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_8)) {
this.writeBlockPosition(this.position);
} else {
this.writeInt(this.position.x);
this.writeByte(this.position.y & 0xFF);
this.writeInt(this.position.z);
}
this.writeInt(this.data);
this.writeBoolean(this.globalEvent);
}
@Override
public void copy(WrapperPlayServerEffect wrapper) {
this.type = wrapper.type;
this.position = wrapper.position;
this.data = wrapper.data;
this.globalEvent = wrapper.globalEvent;
}
public int getType() {
return this.type;
}
public void setType(int type) {
this.type = type;
}
public Vector3i getPosition() {
return this.position;
}
public void setPosition(Vector3i position) {
this.position = position;
}
public int getData() {
return this.data;
}
public void setData(int data) {
this.data = data;
}
public boolean isGlobalEvent() {
return this.globalEvent;
}
public void setGlobalEvent(boolean globalEvent) {
this.globalEvent = globalEvent;
}
}
@@ -45,7 +45,7 @@ public class WrapperPlayServerEntityEquipment extends PacketWrapper<WrapperPlayS
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
entityId = readInt();
} else {
entityId = readVarInt();
@@ -72,7 +72,7 @@ public class WrapperPlayServerEntityEquipment extends PacketWrapper<WrapperPlayS
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(entityId);
} else {
writeVarInt(entityId);
@@ -49,7 +49,7 @@ public class WrapperPlayServerEntityVelocity extends PacketWrapper<WrapperPlaySe
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
entityID = readInt();
} else {
entityID = readVarInt();
@@ -62,7 +62,7 @@ public class WrapperPlayServerEntityVelocity extends PacketWrapper<WrapperPlaySe
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(entityID);
} else {
writeVarInt(entityID);
@@ -77,7 +77,7 @@ public class WrapperPlayServerParticle extends PacketWrapper<WrapperPlayServerPa
int particleTypeId = 0;
ParticleType<?> particleType = null;
boolean v1205 = this.serverVersion.isNewerThanOrEquals(ServerVersion.V_1_20_5);
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
String particleName = readString(64);
particleType = ParticleTypes.getByName("minecraft:" + particleName);
} else if (!v1205) {
@@ -118,7 +118,7 @@ public class WrapperPlayServerParticle extends PacketWrapper<WrapperPlayServerPa
@Override
public void write() {
//TODO on 1.7 we get particle type by 64 len string
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeString(particle.getType().getName().getKey(), 64);
} else if (this.serverVersion.isOlderThan(ServerVersion.V_1_20_5)) {
int id = this.particle.getType().getId(this.serverVersion.toClientVersion());
@@ -71,7 +71,7 @@ public class WrapperPlayServerPlayerInfo extends PacketWrapper<WrapperPlayServer
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
playerDataList = new ArrayList<>(1);
//Only one player data
String rawUsername = readString();
@@ -145,7 +145,7 @@ public class WrapperPlayServerPlayerInfo extends PacketWrapper<WrapperPlayServer
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
//Only one player data can be sent
PlayerData data = playerDataList.get(0);
//We must convert the component string to a normal one
@@ -58,7 +58,7 @@ public class WrapperPlayServerPluginMessage extends PacketWrapper<WrapperPlaySer
else {
this.channelName = readString(20);
}
if (serverVersion == ServerVersion.V_1_7_10) {
if (serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
//We ignore this, because it's not needed.
int legacyDataSize = readShort();
}
@@ -73,7 +73,7 @@ public class WrapperPlayServerPluginMessage extends PacketWrapper<WrapperPlaySer
else {
writeString(this.channelName, 20);
}
if (serverVersion == ServerVersion.V_1_7_10) {
if (serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeShort(data.length);
}
writeBytes(data);
@@ -42,7 +42,7 @@ public class WrapperPlayServerSetExperience extends PacketWrapper<WrapperPlaySer
@Override
public void read() {
experienceBar = readFloat();
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
level = readShort();
totalExperience = readShort();
} else {
@@ -54,7 +54,7 @@ public class WrapperPlayServerSetExperience extends PacketWrapper<WrapperPlaySer
@Override
public void write() {
writeFloat(experienceBar);
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeShort(level);
writeShort(totalExperience);
} else {
@@ -51,7 +51,7 @@ public class WrapperPlayServerSpawnPosition extends PacketWrapper<WrapperPlaySer
this.position = new Vector3i(readLong(), this.serverVersion);
} else {
int x = readInt();
int y = readShort();
int y = readInt();
int z = readInt();
this.position = new Vector3i(x, y, z);
}
@@ -67,7 +67,7 @@ public class WrapperPlayServerSpawnPosition extends PacketWrapper<WrapperPlaySer
writeLong(positionVector);
} else {
writeInt(this.position.x);
writeShort(this.position.y);
writeInt(this.position.y);
writeInt(this.position.z);
}
if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_17)) {
@@ -179,7 +179,7 @@ public class WrapperPlayServerTeams extends PacketWrapper<WrapperPlayServerTeams
prefix = serializer.deserialize(this.readString(16));
suffix = serializer.deserialize(this.readString(16));
optionData = OptionData.values()[readByte()];
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
nameTagVisibility = NameTagVisibility.ALWAYS;
color = NamedTextColor.WHITE;
} else {
@@ -217,7 +217,7 @@ public class WrapperPlayServerTeams extends PacketWrapper<WrapperPlayServerTeams
players = new ArrayList<>();
if (teamMode == TeamMode.CREATE || teamMode == TeamMode.ADD_ENTITIES || teamMode == TeamMode.REMOVE_ENTITIES) {
int size;
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
size = readShort();
} else {
size = readVarInt();
@@ -241,7 +241,7 @@ public class WrapperPlayServerTeams extends PacketWrapper<WrapperPlayServerTeams
writeString(LegacyFormat.trimLegacyFormat(serializer.serialize(info.prefix), 16));
writeString(LegacyFormat.trimLegacyFormat(serializer.serialize(info.suffix), 16));
writeByte(info.optionData.ordinal());
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeString(NameTagVisibility.ALWAYS.getId(), 32);
writeByte(15);
} else {
@@ -274,7 +274,7 @@ public class WrapperPlayServerTeams extends PacketWrapper<WrapperPlayServerTeams
}
if (teamMode == TeamMode.CREATE || teamMode == TeamMode.ADD_ENTITIES || teamMode == TeamMode.REMOVE_ENTITIES) {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeShort(players.size());
} else {
writeVarInt(players.size());
@@ -83,7 +83,7 @@ public class WrapperPlayServerUpdateAttributes extends PacketWrapper<WrapperPlay
@Override
public void read() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
entityID = readInt();
} else {
entityID = readVarInt();
@@ -116,7 +116,7 @@ public class WrapperPlayServerUpdateAttributes extends PacketWrapper<WrapperPlay
double value = readDouble();
int modifiersLength;
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
modifiersLength = readShort();
} else {
modifiersLength = readVarInt();
@@ -144,7 +144,7 @@ public class WrapperPlayServerUpdateAttributes extends PacketWrapper<WrapperPlay
@Override
public void write() {
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeInt(entityID);
} else {
writeVarInt(entityID);
@@ -167,7 +167,7 @@ public class WrapperPlayServerUpdateAttributes extends PacketWrapper<WrapperPlay
}
writeDouble(property.value);
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeShort(property.modifiers.size());
} else {
writeVarInt(property.modifiers.size());
@@ -42,7 +42,7 @@ public class WrapperPlayServerUpdateHealth extends PacketWrapper<WrapperPlayServ
@Override
public void read() {
health = readFloat();
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
food = readShort();
} else {
food = readVarInt();
@@ -53,7 +53,7 @@ public class WrapperPlayServerUpdateHealth extends PacketWrapper<WrapperPlayServ
@Override
public void write() {
writeFloat(health);
if (serverVersion == ServerVersion.V_1_7_10) {
if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeShort(food);
} else {
writeVarInt(food);
@@ -68,7 +68,7 @@ public class WrapperPlayServerUpdateScore extends PacketWrapper<WrapperPlayServe
this.value = Optional.of(this.readVarInt());
this.entityDisplayName = this.readOptional(PacketWrapper::readComponent);
this.scoreFormat = this.readOptional(ScoreFormat::readTyped);
} else if (this.serverVersion == ServerVersion.V_1_7_10) {
} else if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
entityName = readString(16);
action = Action.VALUES[readByte()];
if (action != Action.REMOVE_ITEM) {
@@ -107,7 +107,7 @@ public class WrapperPlayServerUpdateScore extends PacketWrapper<WrapperPlayServe
this.writeVarInt(this.value.orElse(0));
this.writeOptional(this.entityDisplayName, PacketWrapper::writeComponent);
this.writeOptional(this.scoreFormat, ScoreFormat::writeTyped);
} else if (this.serverVersion == ServerVersion.V_1_7_10) {
} else if (this.serverVersion.isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
writeString(entityName, 16);
writeByte(action.ordinal());
if (action != Action.REMOVE_ITEM) {
@@ -19,10 +19,11 @@
package com.github.retrooper.packetevents.wrapper.play.server;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
// Thanks MCProtocolLib, this is taken entirely from them
public class WrapperPlayServerWorldBorder extends PacketWrapper<WrapperPlayServerWorldBorder> {
private WorldBorderAction action;
@@ -92,7 +93,9 @@ public class WrapperPlayServerWorldBorder extends PacketWrapper<WrapperPlayServe
@Override
public void read() {
this.action = WorldBorderAction.values()[this.readVarInt()];
int actionId = readVarInt();
action = serverVersion.isNewerThanOrEquals(ServerVersion.V_1_12) ? WorldBorderAction.fromId(actionId)
: WorldBorderAction.fromLegacyId(actionId);
if (this.action == WorldBorderAction.SET_SIZE) {
this.radius = readDouble();
} else if (this.action == WorldBorderAction.LERP_SIZE) {
@@ -120,7 +123,8 @@ public class WrapperPlayServerWorldBorder extends PacketWrapper<WrapperPlayServe
@Override
public void write() {
writeVarInt(this.action.ordinal());
int actionId = serverVersion.isNewerThanOrEquals(ServerVersion.V_1_12) ? action.getId() : action.getLegacyId();
writeVarInt(actionId);
if (this.action == WorldBorderAction.SET_SIZE) {
writeDouble(this.radius);
} else if (this.action == WorldBorderAction.LERP_SIZE) {
@@ -200,12 +204,79 @@ public class WrapperPlayServerWorldBorder extends PacketWrapper<WrapperPlayServe
return this.warningBlocks;
}
public enum WorldBorderAction {
SET_SIZE,
LERP_SIZE,
SET_CENTER,
INITIALIZE,
SET_WARNING_TIME,
SET_WARNING_BLOCKS;
public void setAction(WorldBorderAction action) {
this.action = action;
}
}
public void setRadius(double radius) {
this.radius = radius;
}
public void setOldRadius(double oldRadius) {
this.oldRadius = oldRadius;
}
public void setNewRadius(double newRadius) {
this.newRadius = newRadius;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public void setCenterX(double centerX) {
this.centerX = centerX;
}
public void setCenterZ(double centerZ) {
this.centerZ = centerZ;
}
public void setPortalTeleportBoundary(int portalTeleportBoundary) {
this.portalTeleportBoundary = portalTeleportBoundary;
}
public void setWarningTime(int warningTime) {
this.warningTime = warningTime;
}
public void setWarningBlocks(int warningBlocks) {
this.warningBlocks = warningBlocks;
}
public enum WorldBorderAction {
SET_SIZE(1),
LERP_SIZE(2),
SET_CENTER(3),
INITIALIZE(6),
SET_WARNING_TIME(5),
SET_WARNING_BLOCKS(4);
private final int legacyId;
WorldBorderAction(int legacyId) {
this.legacyId = legacyId;
}
public int getId() {
return ordinal();
}
public int getLegacyId() {
return legacyId;
}
public static WorldBorderAction fromId(int id) {
return WorldBorderAction.values()[id];
}
public static WorldBorderAction fromLegacyId(int legacyId) {
for (WorldBorderAction action : WorldBorderAction.values()) {
if (action.legacyId == legacyId) {
return action;
}
}
return null;
}
}
}
+19 -45
View File
@@ -1,33 +1,24 @@
import java.io.ByteArrayOutputStream
plugins {
packetevents.`publish-conventions`
}
// TODO UPDATE
val fullVersion = "2.9.0"
val snapshot = true
// properties are all set as string, convert to boolean
ext["snapshot"] = ext["snapshot"].toString().toBooleanStrict()
ext["commitHash"] = providers.exec {
commandLine("git", "rev-parse", "--short", "HEAD")
}.standardOutput.asText.map { it.trim() }.getOrElse("unknown")
ext["versionMeta"] = if (ext["snapshot"] == true) "-SNAPSHOT" else ""
ext["versionMetaWithHash"] = "+${ext["commitHash"]}${ext["versionMeta"]}"
ext["versionNoHash"] = "${ext["fullVersion"]}${ext["versionMeta"]}"
group = "com.github.retrooper"
description = rootProject.name
fun getVersionMeta(includeHash: Boolean): String {
if (!snapshot) {
return ""
}
var commitHash = ""
if (includeHash && file(".git").isDirectory) {
val stdout = ByteArrayOutputStream()
exec {
commandLine("git", "rev-parse", "--short", "HEAD")
standardOutput = stdout
}
commitHash = "+${stdout.toString().trim()}"
}
return "$commitHash-SNAPSHOT"
}
version = "$fullVersion${getVersionMeta(true)}"
ext["versionNoHash"] = "$fullVersion${getVersionMeta(false)}"
version = "${ext["fullVersion"]}${ext[if (ext["snapshot"] == true) "versionMetaWithHash" else "versionMeta"]}"
tasks {
wrapper {
gradleVersion = "8.13"
gradleVersion = "8.14.3"
distributionType = Wrapper.DistributionType.ALL
}
@@ -35,39 +26,22 @@ tasks {
subprojects.filterNot { it.path == ":patch" }.map { it.tasks[task] }.toTypedArray()
}
register("build") {
dependsOn(*taskSubModules("build"))
group = "build"
doLast {
val buildOut = project.layout.buildDirectory.dir("libs").get().asFile
if (!buildOut.exists())
buildOut.mkdirs()
for (subproject in subprojects) {
if (subproject.path.startsWith(":patch")) continue
val subIn = subproject.layout.buildDirectory.dir("libs").get()
copy {
from(subIn)
into(buildOut)
}
}
}
}
register<Delete>("clean") {
dependsOn(*taskSubModules("clean"))
group = "build"
delete(rootProject.layout.buildDirectory)
}
register("printVersion") {
println(project.version)
}
defaultTasks("build")
}
allprojects {
tasks {
withType<Jar> {
archiveBaseName = "${rootProject.name}-${project.name}"
archiveVersion = rootProject.ext["versionNoHash"] as String
}
}
+1
View File
@@ -21,4 +21,5 @@ dependencies {
implementation(libs.fast.util)
implementation(libs.gson)
implementation(libs.java.diff.utils)
implementation(libs.mod.publish)
}
+3 -1
View File
@@ -5,6 +5,7 @@ via-nbt = "4.1.0"
fast-util = "8.5.6"
gson = "2.8.9"
java-diff-utils = "4.12"
mod-publish = "0.8.4"
[libraries]
shadow = { group = "com.gradleup.shadow", name = "shadow-gradle-plugin", version.ref = "shadow" }
@@ -12,4 +13,5 @@ jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version
via-nbt = { group = "com.viaversion", name = "nbt", version.ref = "via-nbt" }
fast-util = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fast-util" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
java-diff-utils = { group = "io.github.java-diff-utils", name = "java-diff-utils", version.ref = "java-diff-utils" }
java-diff-utils = { group = "io.github.java-diff-utils", name = "java-diff-utils", version.ref = "java-diff-utils" }
mod-publish = { group = "me.modmuss50", name = "mod-publish-plugin", version.ref = "mod-publish" }
@@ -0,0 +1,77 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import me.modmuss50.mpp.ModPublishExtension
import me.modmuss50.mpp.PublishModTask
plugins {
me.modmuss50.`mod-publish-plugin`
}
configure<ModPublishExtension> {
changelog = providers.environmentVariable("CHANGELOG")
.map { it.trim() }
.getOrElse("No changelog provided")
type = if (rootProject.ext["snapshot"] == true) BETA else STABLE
dryRun = !hasProperty("noDryPublish")
// if there is no publishing platform set, assume this is the root task
// which is used to support github releases properly
if (!hasProperty("publishing.platform")) {
github {
accessToken = providers.environmentVariable("GITHUB_API_TOKEN")
repository = providers.environmentVariable("GITHUB_REPOSITORY")
commitish = providers.environmentVariable("GITHUB_REF_NAME")
tagName = version.map { "v${it}" }
displayName = version
// won't be empty, just so the publishing plugin won't complain
allowEmptyFiles = true
}
// iterate through all subprojects and search for configured release files
subprojects.filter { it.hasProperty("publishing.platform") }.forEach {
// add main platform publishing file after project has finished evaluating
it.afterEvaluate {
additionalFiles.from(publishMods.file)
}
}
} else {
if (!hasProperty("publishing.skip_files")) {
// setup files for this platform
file = tasks.named<ShadowJar>("shadowJar").flatMap { it.archiveFile }
additionalFiles.from(tasks.named<Jar>("sourcesJar").flatMap { it.archiveFile })
}
val platform = property("publishing.platform").toString()
val fancyPlatform = platform.replaceFirstChar { it.titlecaseChar() }
// setup per-platform modrinth publishing
modrinth {
accessToken = providers.environmentVariable("MODRINTH_API_TOKEN")
var versionBob = "${rootProject.ext["fullVersion"]}+${platform}"
if (rootProject.ext["snapshot"] == true) {
// e.g. "0.0.0+platform.abcdefg"
version = "${versionBob}.${rootProject.ext["commitHash"]}"
} else {
// e.g. "0.0.0+platform"
version = versionBob
}
displayName = "${rootProject.ext["publishing.display_name"]} ${rootProject.version} ${fancyPlatform}"
projectId = property("publishing.modrinth.project").toString()
minecraftVersionRange {
start = property("publishing.modrinth.version.start").toString()
end = property("publishing.modrinth.version.end").toString()
}
val loadersStr = property("publishing.modrinth.loaders").toString()
modLoaders.addAll(loadersStr.split(","))
}
}
}
if (hasProperty("publishing.platform")
&& !hasProperty("publishing.skip_files")
) {
tasks.withType<PublishModTask> {
dependsOn(tasks.named<ShadowJar>("shadowJar"))
dependsOn(tasks.named<Jar>("sourcesJar"))
}
}
@@ -14,6 +14,7 @@ tasks {
shadowJar {
configurations.add(compileShadowOnly)
destinationDirectory = rootProject.layout.buildDirectory.dir("libs")
archiveFileName = "packetevents-${project.name}-${rootProject.ext["versionNoHash"]}.jar"
archiveClassifier = null
@@ -72,8 +73,14 @@ tasks {
configurations.implementation.get().extendsFrom(configurations.shadow.get())
// TODO properly publish correct shadow references instead of this mess
gradle.taskGraph.whenReady {
if (gradle.startParameter.taskNames.any { it.contains("publish") }) {
if (gradle.startParameter.taskNames.any {
it.contains("publish") && !it.equals(
"publishMods",
ignoreCase = true
)
}) {
logger.info("Adding shadow configuration to shadowJar tasks in module ${project.name}.")
tasks.withType<ShadowJar> {
dependencies {
+1
View File
@@ -1,6 +1,7 @@
plugins {
packetevents.`shadow-conventions`
packetevents.`library-conventions`
packetevents.`publish-conventions`
}
repositories {
+4
View File
@@ -0,0 +1,4 @@
publishing.platform=bungeecord
publishing.modrinth.loaders=bungeecord,waterfall
publishing.modrinth.version.start=1.8.8
publishing.modrinth.version.end=latest
+26 -1
View File
@@ -1,5 +1,12 @@
import me.modmuss50.mpp.ModPublishExtension
import me.modmuss50.mpp.PublishModTask
import net.fabricmc.loom.task.RemapJarTask
import net.fabricmc.loom.task.RemapSourcesJarTask
import net.fabricmc.loom.task.prod.ServerProductionRunTask
plugins {
packetevents.`library-conventions`
packetevents.`publish-conventions`
alias(libs.plugins.fabric.loom)
}
@@ -44,13 +51,20 @@ tasks {
}
remapJar {
archiveBaseName = "${rootProject.name}-fabric"
destinationDirectory = rootProject.layout.buildDirectory.dir("libs")
archiveBaseName = "${rootProject.name}-${project.name}"
archiveVersion = rootProject.ext["versionNoHash"] as String
}
remapSourcesJar {
archiveBaseName = "${rootProject.name}-${project.name}"
archiveVersion = rootProject.ext["versionNoHash"] as String
}
withType<PublishModTask> {
dependsOn(named<RemapJarTask>("remapJar"))
dependsOn(named<RemapSourcesJarTask>("remapSourcesJar"))
}
}
loom {
@@ -64,3 +78,14 @@ loom {
accessWidenerPath = sourceSets.main.get().resources.srcDirs.single()
.resolve("${rootProject.name}.accesswidener")
}
configure<ModPublishExtension> {
file = tasks.named<RemapJarTask>("remapJar").flatMap { it.archiveFile }
additionalFiles.from(tasks.named<RemapSourcesJarTask>("remapSourcesJar").flatMap { it.archiveFile })
}
tasks.register<ServerProductionRunTask>("prodServer") {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}
}
+7 -1
View File
@@ -4,7 +4,13 @@ org.gradle.parallel=true
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21.6
minecraft_version=1.21.7
parchment_mappings=2025.06.01
parchment_minecraft_version=1.21.5
loader_version=0.16.14
publishing.skip_files=true
publishing.platform=fabric
publishing.modrinth.loaders=fabric
publishing.modrinth.version.start=1.21
publishing.modrinth.version.end=latest
+7
View File
@@ -1 +1,8 @@
org.gradle.jvmargs=-Xmx2000m
# TODO UPDATE
fullVersion=2.9.3
snapshot=false
publishing.display_name=PacketEvents
publishing.modrinth.project=HYKaKraK
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -2
View File
@@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+2 -2
View File
@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+3 -3
View File
@@ -10,7 +10,7 @@ paper = "1.20.6-R0.1-SNAPSHOT"
bungeecord = "1.21-R0.1-SNAPSHOT"
velocity = "3.4.0-SNAPSHOT"
run-paper = "2.3.1"
fabric-loom = "1.10.5"
fabric-loom = "1.11.4"
spongeGradle = "2.2.0"
classgraph = "4.8.179"
@@ -38,8 +38,8 @@ velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref =
classgraph = { group = "io.github.classgraph", name = "classgraph", version.ref = "classgraph" }
[bundles]
adventure = [ "adventure-api", "adventure-nbt", "adventure-key", "adventure-examination-api", "adventure-examination-string" ]
adventure-serializers = [ "adventure-text-serializer-gson", "adventure-text-serializer-json-legacy", "adventure-text-serializer-legacy" ]
adventure = ["adventure-api", "adventure-nbt", "adventure-key", "adventure-examination-api", "adventure-examination-string"]
adventure-serializers = ["adventure-text-serializer-gson", "adventure-text-serializer-json-legacy", "adventure-text-serializer-legacy"]
[plugins]
run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-paper" }
+1 -1
View File
@@ -16405,7 +16405,7 @@
"bubble_column",
"structure_block"
],
"V_1_7_10": [
"V_1_7_2": [
"air",
"stone",
"grass_block",
+1 -1
View File
@@ -32,7 +32,7 @@
"suggest_command",
"change_page"
],
"V_1_7_10": [
"V_1_7_2": [
"open_url",
"open_file",
"run_command",
+1 -1
View File
@@ -11,7 +11,7 @@
"the_nether": 2,
"the_end": 3
},
"V_1_7_10": {
"V_1_7_2": {
"overworld": 0,
"the_nether": -1,
"the_end": 1
+1 -1
View File
@@ -54,7 +54,7 @@
"diamond",
"turtle_scute"
],
"V_1_7_10": [
"V_1_7_2": [
"leather",
"chainmail",
"iron",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"V_1_7_10": [
"V_1_7_2": [
"white",
"creamy",
"chestnut",
+1 -1
View File
@@ -205,7 +205,7 @@
"luck": 26,
"unluck": 27
},
"V_1_7_10": {
"V_1_7_2": {
"speed": 1,
"slowness": 2,
"haste": 3,
+1 -1
View File
@@ -1,5 +1,5 @@
{
"V_1_7_10": [
"V_1_7_2": [
"red"
],
"V_1_14": [
+1 -1
View File
@@ -136,7 +136,7 @@
"fire",
"donkey_kong"
],
"V_1_7_10": [
"V_1_7_2": [
"kebab",
"aztec",
"alban",
+2 -1
View File
@@ -1,6 +1,7 @@
plugins {
packetevents.`shadow-conventions`
packetevents.`library-conventions`
packetevents.`publish-conventions`
alias(libs.plugins.run.paper)
}
@@ -34,7 +35,7 @@ tasks {
// 1.17 = Java 16
// 1.18 - 1.20.4 = Java 17
// 1.20.5+ = Java 21
val version = "1.21.6"
val version = "1.21.7"
val javaVersion = JavaLanguageVersion.of(21)
val jvmArgsExternal = listOf(
+4
View File
@@ -0,0 +1,4 @@
publishing.platform=spigot
publishing.modrinth.loaders=bukkit,spigot,paper,folia,purpur
publishing.modrinth.version.start=1.8.8
publishing.modrinth.version.end=latest
@@ -19,73 +19,93 @@
package io.github.retrooper.packetevents.bukkit;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.FakeChannelUtil;
import io.github.retrooper.packetevents.injector.SpigotChannelInjector;
import io.github.retrooper.packetevents.manager.player.PlayerManagerImpl;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.plugin.Plugin;
import org.spigotmc.event.player.PlayerSpawnLocationEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.UUID;
/**
* Used on Paper/Spigot before 1.20.5
*/
@NullMarked
@ApiStatus.Internal
public class InternalBukkitListener implements Listener {
static final String KICK_MESSAGE = "PacketEvents failed to inject into a channel";
private final Plugin plugin;
public InternalBukkitListener(Plugin plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onSpawnInGame(PlayerSpawnLocationEvent e) {
Player player = e.getPlayer();
SpigotChannelInjector injector = (SpigotChannelInjector) PacketEvents.getAPI().getInjector();
User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
if (user == null) {
//We did not inject this user
Object channel = PacketEvents.getAPI().getPlayerManager().getChannel(player);
if (channel == null) {
return;
}
//Check if it is a fake connection...
if (!FakeChannelUtil.isFakeChannel(channel) && (!PacketEvents.getAPI().isTerminated() || PacketEvents.getAPI().getSettings().isKickIfTerminated())) {
//Kick them, if they are not a fake player.
FoliaScheduler.getEntityScheduler().runDelayed(player, plugin, (o) -> {
player.kickPlayer("PacketEvents failed to inject into a channel.");
}, null, 0);
}
return;
// this is the first event which is called after the player object has been created;
// note that we can't extract the reference to the player's connection yet
@EventHandler(priority = EventPriority.MONITOR)
public void onLogin(PlayerLoginEvent event) {
if (event.getResult() == PlayerLoginEvent.Result.ALLOWED) {
this.onPreJoin(event.getPlayer());
}
// Set bukkit player object in the injectors
injector.updatePlayer(user, player);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onJoin(PlayerJoinEvent e) {
Player player = e.getPlayer();
Object channel = PacketEvents.getAPI().getPlayerManager().getChannel(player);
SpigotChannelInjector injector = (SpigotChannelInjector) PacketEvents.getAPI().getInjector();
User user = PacketEvents.getAPI().getPlayerManager().getUser(player);
if (user == null) {
//We did not inject this user
//Check if it is a fake connection...
if (channel == null || !FakeChannelUtil.isFakeChannel(channel) && (!PacketEvents.getAPI().isTerminated() || PacketEvents.getAPI().getSettings().isKickIfTerminated())) {
//Kick them, if they are not a fake player.
FoliaScheduler.getEntityScheduler().runDelayed(player, plugin, (o) -> {
player.kickPlayer("PacketEvents failed to inject into a channel.");
}, null, 0);
}
// Set bukkit player object in the injectors
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) {
this.onPostJoin(event.getPlayer());
}
void onPreJoin(Player player) {
// save player in map for packet handler to consume
PacketEventsAPI<?> api = PacketEvents.getAPI();
Map<UUID, WeakReference<Player>> map = ((PlayerManagerImpl) api.getPlayerManager()).joiningPlayers;
map.put(player.getUniqueId(), new WeakReference<>(player));
}
void onPostJoin(Player player) {
PacketEventsAPI<?> api = PacketEvents.getAPI();
User user = api.getPlayerManager().getUser(player);
if (user != null) {
// update player reference in encoder/decoder (doesn't matter if this was already done)
SpigotChannelInjector injector = (SpigotChannelInjector) PacketEvents.getAPI().getInjector();
injector.setPlayer(user.getChannel(), player);
// remove from map; this is probably already removed anyway, but just make sure
((PlayerManagerImpl) api.getPlayerManager()).joiningPlayers.remove(player.getUniqueId());
return;
}
// It's possible that the player reference was set in the prior event.
if (!injector.isPlayerSet(channel)) {
injector.updatePlayer(user, player);
// if this case occurs, we can't extract the connection from a fully joined player
// or have internal connection for this player; this should not occur, kick them
// remove from map just to be sure
((PlayerManagerImpl) api.getPlayerManager()).joiningPlayers.remove(player.getUniqueId());
Object channel = api.getPlayerManager().getChannel(player);
if (channel != null && FakeChannelUtil.isFakeChannel(channel)
|| (api.isTerminated() && !api.getSettings().isKickIfTerminated())) {
// either fake channel or api terminated (and we don't kick)
return;
}
// delay by a tick
FoliaScheduler.getEntityScheduler().runDelayed(player, this.plugin, __ -> {
// only kick if the player is actually still connected
if (player.isConnected()) {
player.kickPlayer(KICK_MESSAGE);
}
}, null, 0);
}
}
@@ -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 io.github.retrooper.packetevents.bukkit;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.FakeChannelUtil;
import io.github.retrooper.packetevents.injector.SpigotChannelInjector;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import static io.github.retrooper.packetevents.bukkit.InternalBukkitListener.KICK_MESSAGE;
/**
* Used on Paper 1.20.5-1.21.6 or Spigot 1.20.5+
*/
@ApiStatus.Internal
@NullMarked
public class InternalBukkitLoginListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onLogin(PlayerLoginEvent event) {
PacketEventsAPI<?> api = PacketEvents.getAPI();
User user = api.getPlayerManager().getUser(event.getPlayer());
if (user != null) {
// if the user can be resolved from this player, save in encoder/decoder
SpigotChannelInjector injector = (SpigotChannelInjector) api.getInjector();
injector.updatePlayer(user, event.getPlayer());
return; // we're done
}
Object channel = api.getPlayerManager().getChannel(event.getPlayer());
if (channel != null && FakeChannelUtil.isFakeChannel(channel)
|| (api.isTerminated() && !api.getSettings().isKickIfTerminated())) {
// either fake channel or api terminated (and we don't kick)
return;
}
// since 1.20.5 and cookie packets, CraftBukkit associates the login listener with the player
// before calling the login event; if this fails on 1.20.5+, something broke a lot
event.disallow(PlayerLoginEvent.Result.KICK_OTHER, KICK_MESSAGE);
}
}
@@ -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 io.github.retrooper.packetevents.bukkit;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.event.UserLoginEvent;
import com.github.retrooper.packetevents.protocol.player.User;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class InternalGlobalBukkitListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
PacketEventsAPI<?> api = PacketEvents.getAPI();
User user = api.getPlayerManager().getUser(event.getPlayer());
PacketEvents.getAPI().getEventManager().callEvent(new UserLoginEvent(user, event.getPlayer()));
}
}
@@ -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 io.github.retrooper.packetevents.bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
import org.spigotmc.event.player.PlayerSpawnLocationEvent;
/**
* Used on Paper 1.21.7+ because of changes due to their Configuration API;
* This is a variant of the pre-1.20.5 {@link InternalBukkitListener}, as Paper
* no longer creates a {@link Player} object during the configuration phase to align with vanilla.
*/
@NullMarked
@ApiStatus.Internal
public class InternalPaperListener implements Listener {
private final InternalBukkitListener delegate;
public InternalPaperListener(Plugin plugin) {
this.delegate = new InternalBukkitListener(plugin);
}
// this may seem like a random event to choose, but this is the first event which
// is called after the player object has been created; note that we can't extract
// the reference to the player's connection yet, like before 1.20.5
@EventHandler(priority = EventPriority.LOWEST)
public void onSpawnLocation(PlayerSpawnLocationEvent event) {
this.delegate.onPreJoin(event.getPlayer());
}
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) {
this.delegate.onPostJoin(event.getPlayer());
}
}
@@ -33,6 +33,9 @@ import com.github.retrooper.packetevents.settings.PacketEventsSettings;
import com.github.retrooper.packetevents.util.LogManager;
import com.github.retrooper.packetevents.util.PEVersion;
import io.github.retrooper.packetevents.bukkit.InternalBukkitListener;
import io.github.retrooper.packetevents.bukkit.InternalBukkitLoginListener;
import io.github.retrooper.packetevents.bukkit.InternalGlobalBukkitListener;
import io.github.retrooper.packetevents.bukkit.InternalPaperListener;
import io.github.retrooper.packetevents.injector.SpigotChannelInjector;
import io.github.retrooper.packetevents.injector.connection.ServerConnectionInitializer;
import io.github.retrooper.packetevents.manager.InternalBukkitPacketListener;
@@ -41,8 +44,8 @@ import io.github.retrooper.packetevents.manager.protocol.ProtocolManagerImpl;
import io.github.retrooper.packetevents.manager.server.ServerManagerImpl;
import io.github.retrooper.packetevents.netty.NettyManagerImpl;
import io.github.retrooper.packetevents.util.BukkitLogManager;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import io.github.retrooper.packetevents.util.SpigotReflectionUtil;
import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import io.github.retrooper.packetevents.util.protocolsupport.ProtocolSupportUtil;
import io.github.retrooper.packetevents.util.viaversion.CustomPipelineUtil;
import io.github.retrooper.packetevents.util.viaversion.ViaVersionUtil;
@@ -156,12 +159,24 @@ public class SpigotPacketEventsBuilder {
getUpdateChecker().handleUpdateCheck();
}
Metrics metrics = new Metrics(plugin, 11327);
//Just to have an idea of which versions of packetevents people use
metrics.addCustomChart(new SimplePie("packetevents_version", () -> getVersion().toStringWithoutSnapshot()));
Bukkit.getPluginManager().registerEvents(new InternalBukkitListener(plugin), plugin);
Bukkit.getPluginManager().registerEvents(new InternalGlobalBukkitListener(), plugin);
try {
// register paper listener to support 1.21.7+ configuration api
Class.forName("io.papermc.paper.connection.PlayerConnection");
Bukkit.getPluginManager().registerEvents(new InternalPaperListener(plugin), plugin);
} catch (ClassNotFoundException ignored) {
if (this.serverManager.getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_5)) {
// register instant-login listener for 1.20.5+
Bukkit.getPluginManager().registerEvents(new InternalBukkitLoginListener(), plugin);
} else {
Bukkit.getPluginManager().registerEvents(new InternalBukkitListener(plugin), plugin);
}
}
if (lateBind) {
//If late-bind is enabled, we still need to inject (after all plugins enabled).
@@ -47,7 +47,6 @@ public class SpigotChannelInjector implements ChannelInjector {
private int connectionChannelsListIndex = -1;
public void updatePlayer(User user, Object player) {
PacketEvents.getAPI().getEventManager().callEvent(new UserLoginEvent(user, player));
Object channel = user.getChannel();
if (channel == null) {
channel = PacketEvents.getAPI().getPlayerManager().getChannel(player);
@@ -1,18 +1,62 @@
package io.github.retrooper.packetevents.manager;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.PacketEventsAPI;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.protocol.ConnectionState;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.LogManager;
import com.github.retrooper.packetevents.wrapper.handshaking.client.WrapperHandshakingClientHandshake;
import com.github.retrooper.packetevents.wrapper.login.server.WrapperLoginServerLoginSuccess;
import io.github.retrooper.packetevents.injector.SpigotChannelInjector;
import io.github.retrooper.packetevents.manager.player.PlayerManagerImpl;
import io.github.retrooper.packetevents.util.protocolsupport.ProtocolSupportUtil;
import io.github.retrooper.packetevents.util.viaversion.ViaVersionUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.UUID;
@ApiStatus.Internal
public class InternalBukkitPacketListener extends com.github.retrooper.packetevents.manager.InternalPacketListener {
@Override
public void onPacketSend(PacketSendEvent event) {
super.onPacketSend(event);
// process after generic internal listener has processed this packet
if (event.getPacketType() == PacketType.Login.Server.LOGIN_SUCCESS) {
WrapperLoginServerLoginSuccess packet = new WrapperLoginServerLoginSuccess(event);
this.tryUpdatePlayerReference(event, event.getUser(), packet.getUserProfile().getUUID());
} else if (event.getPacketType() == PacketType.Play.Server.JOIN_GAME) {
// try to update player reference again
this.tryUpdatePlayerReference(event, event.getUser(), event.getUser().getUUID());
}
}
private void tryUpdatePlayerReference(PacketSendEvent event, User user, UUID playerId) {
PacketEventsAPI<?> api = PacketEvents.getAPI();
Map<UUID, WeakReference<Player>> map = ((PlayerManagerImpl) api.getPlayerManager()).joiningPlayers;
WeakReference<Player> playerRef = map.remove(playerId);
Player player = playerRef != null ? playerRef.get() : null;
// we don't care whether this player is null or not; if it is null,
// our bukkit listener may have already handled everything or will be handling it
if (player != null) {
((SpigotChannelInjector) api.getInjector()).updatePlayer(user, player);
if (api.getLogManager().isDebug()) {
api.getLogManager().debug("Updated player reference on packet handling for " + player.getUniqueId());
}
// update player object in current packet event
event.setPlayer(player);
}
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Handshaking.Client.HANDSHAKE) {
@@ -29,11 +29,29 @@ import io.github.retrooper.packetevents.util.SpigotReflectionUtil;
import io.github.retrooper.packetevents.util.protocolsupport.ProtocolSupportUtil;
import io.github.retrooper.packetevents.util.viaversion.ViaVersionUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class PlayerManagerImpl implements PlayerManager {
// we associate the user object with a UUID upon receiving the ClientboundGameProfilePacket;
// but, as packets are send asynchronously, this causes a race condition between the packet being sent
// and the main thread login logic; if the packet gets processed by packetevents before the main thread login
// logic reaches the event where we process things, everything is fine; if not, the event won't know what
// to do with the player and will kick them
//
// this map gets used early on in the login/join process, were we can't extract
// a reference to the player's connection from the player;
// this player reference may then get used by the packet handler to associate the user with a player
// from inside the packet handler, ensuring consistent results regardless of which thread is faster
@ApiStatus.Internal
public final Map<UUID, WeakReference<Player>> joiningPlayers = new ConcurrentHashMap<>();
@Override
public int getPing(@NotNull Object player) {
//Yay, we contributed this to Spigot and now we can use it on 1.17+ servers.
@@ -173,10 +173,10 @@ public class SpigotConversionUtil {
Object nbt = SpigotReflectionUtil.convertWorldServerDimensionToNMSNbt(serverLevel);
NBTCompound peNbt = SpigotReflectionUtil.fromMinecraftNBT(nbt);
ResourceLocation dimensionName = new ResourceLocation(SpigotReflectionUtil.getDimensionKey(serverLevel));
int dimensionId = SpigotReflectionUtil.getDimensionId(serverLevel);
ResourceLocation dimensionTypeName = new ResourceLocation(SpigotReflectionUtil.getDimensionKey(serverLevel));
int dimensionTypeId = SpigotReflectionUtil.getDimensionId(serverLevel);
return DimensionType.decode(peNbt, version.toClientVersion(),
new SimpleTypesBuilderData(dimensionName, dimensionId));
new SimpleTypesBuilderData(dimensionTypeName, dimensionTypeId));
}
}
@@ -33,19 +33,35 @@ import com.github.retrooper.packetevents.wrapper.PacketWrapper;
import com.google.common.collect.BiMap;
import com.google.common.collect.MapMaker;
import io.netty.buffer.PooledByteBufAllocator;
import org.bukkit.*;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Particle;
import org.bukkit.Registry;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
@@ -79,7 +95,7 @@ public final class SpigotReflectionUtil {
//Minecraft classes
public static Class<?> MINECRAFT_SERVER_CLASS, NMS_PACKET_DATA_SERIALIZER_CLASS, NMS_ITEM_STACK_CLASS,
NMS_IMATERIAL_CLASS, NMS_ENTITY_CLASS, ENTITY_PLAYER_CLASS, BOUNDING_BOX_CLASS, NMS_MINECRAFT_KEY_CLASS,
ENTITY_HUMAN_CLASS, PLAYER_CONNECTION_CLASS, SERVER_COMMON_PACKETLISTENER_IMPL_CLASS, SERVER_CONNECTION_CLASS, NETWORK_MANAGER_CLASS, NMS_ENUM_PARTICLE_CLASS,
ENTITY_HUMAN_CLASS, PLAYER_CONNECTION_CLASS, TRANSFER_COOKIE_CONNECTION_CLASS, SERVER_LOGIN_PACKET_LISTENER_IMPL_CLASS, SERVER_COMMON_PACKETLISTENER_IMPL_CLASS, SERVER_CONNECTION_CLASS, NETWORK_MANAGER_CLASS, NMS_ENUM_PARTICLE_CLASS,
MOB_EFFECT_LIST_CLASS, NMS_ITEM_CLASS, DEDICATED_SERVER_CLASS, LEVEL_CLASS, SERVER_LEVEL_CLASS, ENUM_PROTOCOL_DIRECTION_CLASS,
GAME_PROFILE_CLASS, CRAFT_WORLD_CLASS, CRAFT_SERVER_CLASS, CRAFT_PLAYER_CLASS, CRAFT_ENTITY_CLASS, CRAFT_ITEM_STACK_CLASS, CRAFT_PARTICLE_CLASS,
LEVEL_ENTITY_GETTER_CLASS, ENTITY_ACCESS_CLASS, PERSISTENT_ENTITY_SECTION_MANAGER_CLASS, PAPER_ENTITY_LOOKUP_CLASS, CRAFT_MAGIC_NUMBERS_CLASS, IBLOCK_DATA_CLASS,
@@ -95,7 +111,7 @@ public final class SpigotReflectionUtil {
//Fields
public static Field ENTITY_PLAYER_PING_FIELD, ENTITY_BOUNDING_BOX_FIELD, BYTE_BUF_IN_PACKET_DATA_SERIALIZER, DIMENSION_CODEC_FIELD,
DYNAMIC_OPS_NBT_INSTANCE_FIELD, CHUNK_PROVIDER_SERVER_FIELD, CRAFT_PARTICLE_PARTICLES_FIELD, NMS_MK_KEY_FIELD, LEGACY_NMS_PARTICLE_KEY_FIELD, LEGACY_NMS_KEY_TO_NMS_PARTICLE,
REMOTE_CHAT_SESSION_FIELD;
REMOTE_CHAT_SESSION_FIELD, REGISTRY_KEY_LOCATION_FIELD;
//Methods
public static Method IS_DEBUGGING, GET_CRAFT_PLAYER_HANDLE_METHOD, GET_CRAFT_ENTITY_HANDLE_METHOD, GET_CRAFT_WORLD_HANDLE_METHOD,
@@ -108,7 +124,7 @@ public final class SpigotReflectionUtil {
GET_DIMENSION_MANAGER, GET_DIMENSION_ID, GET_DIMENSION_KEY, CODEC_ENCODE_METHOD, DATA_RESULT_GET_METHOD,
READ_NBT_FROM_STREAM_METHOD, WRITE_NBT_TO_STREAM_METHOD, STREAM_DECODER_DECODE, STREAM_ENCODER_ENCODE,
CREATE_REGISTRY_RESOURCE_KEY, GET_REGISTRY_OR_THROW, GET_DIMENSION_TYPES, GET_REGISTRY_ID,
NBT_ACCOUNTER_UNLIMITED_HEAP, GET_REGISTRY_KEY_LOCATION, CHUNK_CACHE_GET_IBLOCKACCESS, CHUNK_CACHE_GET_ICHUNKACCESS,
NBT_ACCOUNTER_UNLIMITED_HEAP, CHUNK_CACHE_GET_IBLOCKACCESS, CHUNK_CACHE_GET_ICHUNKACCESS,
IBLOCKACCESS_GET_BLOCK_DATA, CHUNK_GET_BLOCK_DATA, PLAYER_CHUNK_MAP_GET_PLAYER_CHUNK, PLAYER_CHUNK_GET_CHUNK;
//Constructors
@@ -166,10 +182,7 @@ public final class SpigotReflectionUtil {
GET_ENTITY_BY_ID_LEVEL_ENTITY_GETTER_METHOD = Reflection.getMethod(LEVEL_ENTITY_GETTER_CLASS, ENTITY_ACCESS_CLASS, 0, int.class);
}
if (DIMENSION_MANAGER_CLASS != null) {
if (PacketEvents.getAPI().getServerManager().getVersion() == ServerVersion.V_1_16
|| PacketEvents.getAPI().getServerManager().getVersion() == ServerVersion.V_1_16_1) {
GET_DIMENSION_KEY = Reflection.getMethod(LEVEL_CLASS, "getTypeKey", 0);
}
GET_DIMENSION_KEY = Reflection.getMethod(LEVEL_CLASS, "getTypeKey", 0);
GET_DIMENSION_MANAGER = Reflection.getMethod(LEVEL_CLASS, DIMENSION_MANAGER_CLASS, 0);
GET_DIMENSION_ID = Reflection.getMethod(DIMENSION_MANAGER_CLASS, int.class, 0);
}
@@ -243,7 +256,6 @@ public final class SpigotReflectionUtil {
0, RESOURCE_KEY);
GET_DIMENSION_TYPES = Reflection.getMethod(REGISTRY_ACCESS_FROZEN, REGISTRY, 0);
GET_REGISTRY_ID = Reflection.getMethod(REGISTRY, int.class, 0, Object.class);
GET_REGISTRY_KEY_LOCATION = Reflection.getMethod(RESOURCE_KEY, NMS_MINECRAFT_KEY_CLASS, 0);
//Only need to check if the arguments are null. The lookup class being null is handled in the method.
//Not checking if the arguments passed are null could lead to unintended behavior.
if (IBLOCKACCESS_CLASS != null) {
@@ -289,6 +301,7 @@ public final class SpigotReflectionUtil {
}
REMOTE_CHAT_SESSION_FIELD = Reflection.getField(ENTITY_PLAYER_CLASS, REMOTE_CHAT_SESSION_CLASS, 0);
REGISTRY_KEY_LOCATION_FIELD = Reflection.getField(RESOURCE_KEY, NMS_MINECRAFT_KEY_CLASS, 1);
}
private static void initClasses() {
@@ -305,9 +318,12 @@ public final class SpigotReflectionUtil {
NMS_MINECRAFT_KEY_CLASS = getServerClass(IS_OBFUSCATED ? "resources.MinecraftKey" : "resources.ResourceLocation", "MinecraftKey");
ENTITY_HUMAN_CLASS = getServerClass(IS_OBFUSCATED ? "world.entity.player.EntityHuman" : "world.entity.player.Player", "EntityHuman");
PLAYER_CONNECTION_CLASS = getServerClass(IS_OBFUSCATED ? "server.network.PlayerConnection" : "server.network.ServerGamePacketListenerImpl", "PlayerConnection");
SERVER_LOGIN_PACKET_LISTENER_IMPL_CLASS = getServerClass(IS_OBFUSCATED ? "server.network.LoginListener" : "server.network.ServerLoginPacketListenerImpl", "LoginListener");
//Only on 1.20.2
// 1.20.2+
SERVER_COMMON_PACKETLISTENER_IMPL_CLASS = getServerClass("server.network.ServerCommonPacketListenerImpl", "ServerCommonPacketListenerImpl");
// 1.20.5+
TRANSFER_COOKIE_CONNECTION_CLASS = getOBCClass("entity.CraftPlayer$TransferCookieConnection");
SERVER_CONNECTION_CLASS = getServerClass(IS_OBFUSCATED ? "server.network.ServerConnection" : "server.network.ServerConnectionListener", "ServerConnection");
NETWORK_MANAGER_CLASS = getServerClass(IS_OBFUSCATED ? "network.NetworkManager" : "network.Connection", "NetworkManager");
@@ -530,6 +546,9 @@ public final class SpigotReflectionUtil {
return null;
}
ReflectionObject wrappedEntityPlayer = new ReflectionObject(entityPlayer, ENTITY_PLAYER_CLASS);
if (TRANSFER_COOKIE_CONNECTION_CLASS != null) {
return wrappedEntityPlayer.readObject(0, TRANSFER_COOKIE_CONNECTION_CLASS);
}
return wrappedEntityPlayer.readObject(0, SpigotReflectionUtil.PLAYER_CONNECTION_CLASS);
}
@@ -579,8 +598,13 @@ public final class SpigotReflectionUtil {
if (playerConnection == null) {
return null;
}
Class<?> playerConnectionClass = SERVER_COMMON_PACKETLISTENER_IMPL_CLASS != null ?
SERVER_COMMON_PACKETLISTENER_IMPL_CLASS : PLAYER_CONNECTION_CLASS;
Class<?> playerConnectionClass;
if (SERVER_COMMON_PACKETLISTENER_IMPL_CLASS != null) {
playerConnectionClass = playerConnection.getClass() == SERVER_LOGIN_PACKET_LISTENER_IMPL_CLASS
? SERVER_LOGIN_PACKET_LISTENER_IMPL_CLASS : SERVER_COMMON_PACKETLISTENER_IMPL_CLASS;
} else {
playerConnectionClass = PLAYER_CONNECTION_CLASS;
}
ReflectionObject wrapper = new ReflectionObject(playerConnection, playerConnectionClass);
try {
return wrapper.readObject(0, NETWORK_MANAGER_CLASS);
@@ -707,7 +731,7 @@ public final class SpigotReflectionUtil {
public static String getDimensionKey(Object worldServer) {
try {
Object resourceKey = GET_DIMENSION_KEY.invoke(worldServer);
return GET_REGISTRY_KEY_LOCATION.invoke(resourceKey).toString();
return REGISTRY_KEY_LOCATION_FIELD.get(resourceKey).toString();
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
+1
View File
@@ -4,6 +4,7 @@ import org.spongepowered.plugin.metadata.model.PluginDependency
plugins {
packetevents.`shadow-conventions`
packetevents.`library-conventions`
packetevents.`publish-conventions`
alias(libs.plugins.spongeGradle)
}
+4
View File
@@ -0,0 +1,4 @@
publishing.platform=sponge
publishing.modrinth.loaders=sponge
publishing.modrinth.version.start=1.21
publishing.modrinth.version.end=latest
@@ -18,6 +18,7 @@
package io.github.retrooper.packetevents.sponge;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.UserLoginEvent;
import com.github.retrooper.packetevents.protocol.player.User;
import com.github.retrooper.packetevents.util.FakeChannelUtil;
import io.github.retrooper.packetevents.sponge.injector.SpongeChannelInjector;
@@ -49,6 +50,7 @@ public class InternalSpongeListener {
}
// Set player object in the injectors
PacketEvents.getAPI().getEventManager().callEvent(new UserLoginEvent(user, player));
injector.updatePlayer(user, player);
}
@@ -50,7 +50,6 @@ public class SpongeChannelInjector implements ChannelInjector {
private int connectionChannelsListIndex = -1;
public void updatePlayer(User user, Object player) {
PacketEvents.getAPI().getEventManager().callEvent(new UserLoginEvent(user, player));
Object channel = user.getChannel();
if (channel == null) {
channel = PacketEvents.getAPI().getPlayerManager().getChannel(player);
+1
View File
@@ -1,6 +1,7 @@
plugins {
packetevents.`shadow-conventions`
packetevents.`library-conventions`
packetevents.`publish-conventions`
alias(libs.plugins.run.velocity)
}
+4
View File
@@ -0,0 +1,4 @@
publishing.platform=velocity
publishing.modrinth.loaders=velocity
publishing.modrinth.version.start=1.7.10
publishing.modrinth.version.end=latest