Compare commits

..
Author SHA1 Message Date
Tim Langner 9cb125fb1f Update to Minecraft 26.3 (#3681)
* Update to Minecraft 26.3

- Bump the server dependency, latest version and plugin api-version to 26.3
- Add the configuration POST_EFFECTS packet and shift the following ids
- Add the CHANGE_DESTROY_DIRECTION player dig type
- Stop resolving the legacy particle enum on 1.13+, the 26.3 particle packet has an unrelated enum
- Read optional message signatures in getMessageSignatures
- Cache parameterized structure modifiers separately from the raw field type
- Add WrappedVecDelta and WrappedPositionPath for the new entity movement packets
- Update tests for the 26.3 server classes

* Change default class type from null to void.class
2026-11-03 21:36:13 -06:00
Mark Cockram 0f7670e13d Fix recursive update in StructureCache on legacy versions (#3682)
ConcurrentHashMap.computeIfAbsent rejects re-entrant updates on Java 9+,
and determineBestCreator can re-enter the cache. On 1.8 it also recursed
through PacketRegistry.getStreamCodec into createOldRegister, so only ask
for stream codecs on 1.20.5+, where they exist.
2026-11-03 21:25:51 -06:00
Rishabh Venu 47f465d3f5 Run sync listeners of bundled packets on the main thread (#3674)
* Run sync listeners of bundled packets on the main thread

processOutbound only checked the BUNDLE packet type for main-thread listeners before the bundle was unpacked, so a synchronous listener registered for a packet inside the bundle ran on the thread the bundle was sent from.

Fixes #3665

* Keep the network marker when rescheduling a bundled packet
2026-11-03 21:24:38 -06:00
Dan Mulloy 501e187342 Update links 2026-10-26 08:41:41 -05:00
Rishabh Venu 6720a70dbd Use the stream codec in WirePacket on 1.20.5 and newer (#3676) 2026-10-24 02:42:11 +00:00
Rishabh Venu 3d8e597244 Replace the inbound chat example in the README (#3675) 2026-10-23 22:34:30 -04:00
LOOHP 58d45e79b1 Fix Early Channel Injection Ordering on Paper (#3679) 2026-10-23 21:21:29 -05:00
1wairesd d89a5deb3e Fix NoClassDefFoundError: CommandProtocol$1 on Paper plugin classloader (#3673) 2026-10-06 22:02:46 +00:00
Diogo Correia 1ea47d63ba Fix WrappedLevelChunkData in MC 26.3 (#3671) 2026-10-03 18:18:42 +00:00
Diogo Correia 3c616caf74 Fix asCraftMirror method accessor in MC 26.3 (#3672) 2026-10-03 11:18:45 -05:00
Richard Strunk 583353e9fa Fix Minecraft 26.3 play packet mappings (#3670)
Fix 26.3 play packet mappings
2026-09-24 23:02:31 -05:00
Dan Mulloy f0e959a1ad Fix WrappedDataWatcher in 1.8
Fixes #3557
2026-08-23 20:06:32 -05:00
Dan Mulloy cbe7fd87e4 Fix WrappedGameProfile in 1.21.10
Fixes #3573
2026-08-23 19:55:06 -05:00
Dan Mulloy 3fab29d3c6 Fix WrappedStatistic in modern versions
Fixes #3591
2026-08-23 19:35:28 -05:00
Dan Mulloy fec45cf8fc Restore Java 17 compat for Spigot too 2026-08-23 14:34:29 -05:00
Dan Mulloyandjustcarlux 5c6c773935 Prioritize searching for singletons when determining best creators
Fixes #3663

Co-authored-by: justcarlux <manuelitojuan691@gmail.com>
2026-08-23 14:30:03 -05:00
kik 78b10165ff Fix: Java 17 bytecode target on paper module (#3662)
Fixes #3661
2026-08-19 12:51:56 -05:00
Dan Mulloyandrkfsociety dda284cd7a Fix custom payload handling on modern servers
Co-authored-by: rkfsociety <romankuzminvital@gmail.com>
2026-08-17 21:50:35 -05:00
Dan MulloyandSpagles debdd6d9a2 Fix async packet handling and utility edge cases
Co-authored-by: Spagles <106791090+Spagles@users.noreply.github.com>
2026-08-17 21:23:40 -05:00
Dan Mulloy 2c53c29cf7 Only copy bundle packets when something changes
Fixes #3596
2026-08-17 20:58:33 -05:00
Dan Mulloy 1ef8431764 Fix some issues with bundle packets
Addresses #3596
2026-08-17 20:43:23 -05:00
Dan Mulloy 90eb2a6558 Fix item particles in 26.1+
Fixes #3658
2026-08-17 20:29:16 -05:00
Dan Mulloy 67ce937109 Create modern Paper plugin artifact (#3656) 2026-08-02 20:34:39 -05:00
585 changed files with 3411 additions and 34812 deletions

No files matched your search

+8 -5
View File
@@ -6,15 +6,17 @@ development, so keep changes focused and preserve existing behavior unless a bre
## Building ProtocolLib
Use the Gradle wrapper from the repository root. The build uses the Java 25 toolchain and resolves the current Spigot
server dependency from the CodeMC repository.
Use the Gradle wrapper from the repository root. The root project builds the platform-neutral library published to
Maven. The `paper` module compiles and tests it against Paper's development bundle and produces the primary plugin,
`build/libs/ProtocolLib.jar`. The `spigot` compatibility module produces `build/libs/ProtocolLib-Spigot.jar`.
```shell
./gradlew test
./gradlew build shadowJar
```
`shadowJar` produces `build/libs/ProtocolLib.jar` with the required Byte Buddy classes included.
`shadowJar` builds both plugin distributions with the required Byte Buddy classes included. Neither platform provider
nor plugin descriptor is included in the Maven core artifact.
ProtocolLib uses the Java 25 toolchain to compile against the current server, but production classes target Java 17 for
compatibility with older supported servers.
@@ -26,7 +28,7 @@ server classes. The server packet classes and codecs are authoritative when docu
### 1. Update version metadata
- Update `mcVersion` in `build.gradle.kts`.
- Update `mcVersion` in `gradle.properties`.
- Add the release to `MinecraftVersion` and update `MinecraftVersion.LATEST`.
- Update `ProtocolLibrary.MAXIMUM_MINECRAFT_VERSION` and `ProtocolLibrary.MINECRAFT_LAST_RELEASE_DATE`.
- Update `MinecraftProtocolVersion` if the release changed the protocol number.
@@ -47,6 +49,7 @@ serverbound and clientbound packet classes and codecs against every protocol pha
- Inspect changed fields, constructors, records, nested classes, and stream codecs instead of relying only on class names.
- Prefer structural or type-based reflection where names are unstable.
- Treat the current server API shape as primary and use older supported shapes only as narrow compatibility fallbacks.
- Put version checks at the narrowest compatibility boundary and leave the older path unchanged.
- If an NMS concept was removed, report it as unsupported or absent rather than treating a different class as equivalent.
- Preserve packet fields that are unrelated to the value being changed.
@@ -57,7 +60,7 @@ serverbound and clientbound packet classes and codecs against every protocol pha
### 4. Add regression coverage
- Initialize the test environment with `BukkitInitialization` when tests access CraftBukkit or NMS classes.
- Exercise the actual packet structure supplied by the current Spigot dependency.
- Exercise the actual packet structure supplied by the current Paper dependency.
- Cover both sides of a version guard when feasible.
- Re-enable previously disabled tests when the underlying incompatibility is fixed.
- Run targeted tests while developing, followed by `./gradlew test`.
+4 -2
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
java-version: '25'
cache: 'gradle'
- name: Run gradle build lifecycle
@@ -27,4 +27,6 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ProtocolLib
path: build/libs/ProtocolLib.jar
path: |
build/libs/ProtocolLib.jar
build/libs/ProtocolLib-Spigot.jar
+5 -2
View File
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
java-version: '25'
cache: 'gradle'
- name: Run gradle build lifecycle
@@ -27,7 +27,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ProtocolLib
path: build/libs/ProtocolLib.jar
path: |
build/libs/ProtocolLib.jar
build/libs/ProtocolLib-Spigot.jar
- name: Create or update GitHub pre-release
env:
@@ -41,3 +43,4 @@ jobs:
NEW_NOTES="$RELEASE_NOTES"$'\n'"* [$SHORT_HASH] $COMMIT_MSG"
gh release edit dev-build --notes "$NEW_NOTES" --repo ${{ github.repository }}
gh release upload dev-build build/libs/ProtocolLib.jar#ProtocolLib.jar --clobber --repo ${{ github.repository }}
gh release upload dev-build build/libs/ProtocolLib-Spigot.jar#ProtocolLib-Spigot.jar --clobber --repo ${{ github.repository }}
+1
View File
@@ -60,6 +60,7 @@ renamed packet can abort registration of later packets in the same registry.
## Version Compatibility
- Put version checks at the narrowest affected boundary.
- Treat the current supported server API shape as the primary path. Use older supported shapes only as narrow fallbacks.
- Keep the pre-change code path intact whenever possible.
- Use named `MinecraftVersion` constants with `atOrAbove()` instead of scattered version strings.
- A public method should keep the same signature and broad contract across versions.
-368
View File
@@ -1,368 +0,0 @@
# Packet Wrappers
Type-safe wrappers around ProtocolLib `PacketContainer` objects.
Each wrapper lives under `net.dmulloy2.protocol.wrappers`, organized by protocol phase then direction.
---
## Package layout
```
net.dmulloy2.protocol/
AbstractPacket.java # base class for all wrappers
wrappers/
game/
clientbound/
WrappedClientbound<PacketName>Packet.java
serverbound/
WrappedServerbound<PacketName>Packet.java
login/
clientbound/ …
serverbound/ …
status/
clientbound/ …
serverbound/ …
```
New NMS types that are reusable across multiple packets belong in the core ProtocolLib packages, not here:
| What | Where |
|---|---------------------------------------------------------------------------------------|
| New enum wrapper | inner enum + registration in `com.comphenix.protocol.wrappers.EnumWrappers |
| New structure wrapper | `com.comphenix.protocol.wrappers.Wrapped<Name>.java` + getter in `AbstractStructure` |
| Packet wrapper | `net.dmulloy2.protocol.wrappers.<phase>.<direction>.Wrapped<NMSPacketName>` |
---
## Class Structure
* Every wrapper extends `AbstractPacket`
* There should be 3 constructors: no-args (create new packet with default values), all-args (create new packet with specified values), and from-container (wrap existing packet)
* Then create getters and setters for each field in the packet, using the appropriate `StructureModifier` methods from `AbstractStructure`.
```java
package net.dmulloy2.protocol.wrappers.game.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.PacketConstructor;
import net.dmulloy2.protocol.AbstractPacket;
public class WrappedServerboundAcceptTeleportationPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Client.TELEPORT_ACCEPT;
public WrappedServerboundAcceptTeleportationPacket() {
super(new PacketContainer(TYPE), TYPE);
handle.getModifier().writeDefaults();
}
public WrappedServerboundAcceptTeleportationPacket(int id) {
this(PacketConstructor.DEFAULT.withPacket(TYPE, new Class<?>[] { int.class }).createPacket(id));
}
public WrappedServerboundAcceptTeleportationPacket(PacketContainer packet) {
super(packet, TYPE);
}
// getters and setters for packet fields
}
```
`AbstractPacket` validates the type and exposes `getHandle()`, `sendPacket(Player)`, and `receivePacket(Player)`.
---
## Reading and writing packet fields
Always go through `AbstractStructure`'s typed getters. Never touch `getModifier()` directly or read raw field indices without a type-checked accessor.
### Primitives
```java
handle.getIntegers().read(0) // int
handle.getIntegers().write(0, value)
handle.getBooleans().read(0) // boolean
handle.getBooleans().write(0, value)
handle.getFloat().read(0) // float (note: singular, not getFloats)
handle.getFloat().write(0, value)
handle.getLongs() / getDoubles() / getBytes() / getShorts() / getStrings() …
```
Field indices within a type are zero-based and ordered by their declaration order in the NMS class.
### Bukkit / ProtocolLib wrapper types
```java
handle.getVectors().read(0) // org.bukkit.util.Vector ↔ Vec3
handle.getPositionMoveRotations().read(0) // WrappedPositionMoveRotation ↔ PositionMoveRotation
handle.getEntityModifier(world).read(0) // org.bukkit.entity.Entity
handle.getItemModifier().read(0) // org.bukkit.inventory.ItemStack
handle.getBlockPositionModifier().read(0) // BlockPosition
handle.getNbtModifier().read(0) // NbtBase<?>
handle.getDataWatcherModifier().read(0) // WrappedDataWatcher
// …see AbstractStructure for the full list
```
### Enums
Use `EnumWrappers.get<Name>Converter()` with the appropriate accessor:
```java
// Single enum field
handle.getEnumModifier(EnumWrappers.Hand.class, EnumWrappers.getHandClass()).read(0)
// Set of enum values (e.g. RelativeArgument)
handle.getSets(EnumWrappers.getRelativeArgumentConverter()).read(0)
handle.getSets(EnumWrappers.getRelativeArgumentConverter()).write(0, set)
```
### Nested structures (InternalStructure)
Only use `getStructures()` when you genuinely need to inspect a sub-structure that has no dedicated typed wrapper yet. Once you have written a `Wrapped<Name>` class, add a getter to `AbstractStructure` and use that instead.
---
## Adding a new NMS enum wrapper
1. **Declare** the enum as an inner class of `EnumWrappers`, matching NMS constant names exactly:
```java
// EnumWrappers.java
public enum RelativeArgument {
X, Y, Z, Y_ROT, X_ROT, DELTA_X, DELTA_Y, DELTA_Z, ROTATE_DELTA
}
```
2. **Register** the NMS class, class getter, converter, and `associate()` call — all following the same pattern as `Hand`, `Direction`, etc.:
```java
// static field
private static Class<?> RELATIVE_CLASS = null;
// inside initialize() — class loading
RELATIVE_CLASS = MinecraftReflection.getMinecraftClass("world.entity.Relative");
// inside initialize() — association
associate(RELATIVE_CLASS, RelativeArgument.class, getRelativeArgumentConverter());
// public API
public static Class<?> getRelativeArgumentClass() {
initialize();
return RELATIVE_CLASS;
}
public static EquivalentConverter<RelativeArgument> getRelativeArgumentConverter() {
return new EnumConverter<>(getRelativeArgumentClass(), RelativeArgument.class);
}
```
`EnumConverter` maps by constant name (`Enum.valueOf`). Use it whenever the NMS class **is** a real Java enum and the constant names match. Use `FauxEnumConverter` only for NMS classes that are enum-like but not `java.lang.Enum`.
---
## Adding a new NMS structure wrapper
`PositionMoveRotation` is the reference example.
### 1 — Create `com.comphenix.protocol.wrappers.Wrapped<Name>.java`
```java
public class WrappedPositionMoveRotation {
// mutable fields matching the NMS record
/** Read from a live InternalStructure using AbstractStructure typed getters. */
public static WrappedPositionMoveRotation fromHandle(InternalStructure handle) {
WrappedPositionMoveRotation w = new WrappedPositionMoveRotation();
Vector pos = handle.getVectors().read(0);
Vector delta = handle.getVectors().read(1);
w.x = pos.getX(); w.y = pos.getY(); w.z = pos.getZ();
w.deltaX = delta.getX(); …
w.yaw = handle.getFloat().read(0);
w.pitch = handle.getFloat().read(1);
return w;
}
/**
* Returns an EquivalentConverter for use in StructureModifier.withType().
*
* getSpecific — delegates to fromHandle(InternalStructure)
* getGeneric — constructs a new NMS record via its public constructor
*/
public static EquivalentConverter<WrappedPositionMoveRotation> getConverter() {
return new EquivalentConverter<>() {
@Override
public WrappedPositionMoveRotation getSpecific(Object generic) {
return fromHandle(InternalStructure.getConverter().getSpecific(generic));
}
@Override
public Object getGeneric(WrappedPositionMoveRotation s) {
try {
Class<?> vec3 = MinecraftReflection.getVec3DClass();
Class<?> pmr = MinecraftReflection.getMinecraftClass("world.entity.PositionMoveRotation");
Constructor<?> v = vec3.getDeclaredConstructor(double.class, double.class, double.class);
Constructor<?> p = pmr.getDeclaredConstructor(vec3, vec3, float.class, float.class);
return p.newInstance(v.newInstance(s.x, s.y, s.z),
v.newInstance(s.deltaX, s.deltaY, s.deltaZ),
s.yaw, s.pitch);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override public Class<WrappedPositionMoveRotation> getSpecificType() {
return WrappedPositionMoveRotation.class;
}
};
}
}
```
**Reading** always goes through `AbstractStructure` getters (`getVectors()`, `getFloat()`, …) — never raw field accessors.
**Writing** must construct a fresh NMS instance when the NMS type is an immutable record (final fields). Mutable NMS classes can be modified via their own `InternalStructure` instead.
### 2 — Add a getter to `AbstractStructure`
```java
// AbstractStructure.java — alongside getVectors(), getIntegers(), etc.
public StructureModifier<WrappedPositionMoveRotation> getPositionMoveRotations() {
return structureModifier.withType(
MinecraftReflection.getMinecraftClass("world.entity.PositionMoveRotation"),
WrappedPositionMoveRotation.getConverter());
}
```
### 3 — Use it in the packet wrapper
```java
public WrappedPositionMoveRotation getChange() {
return handle.getPositionMoveRotations().read(0);
}
public void setChange(WrappedPositionMoveRotation change) {
handle.getPositionMoveRotations().write(0, change);
}
```
---
## Unit tests
Each wrapper class gets its own test file named `<WrapperClassName>Test.java`, co-located with the wrapper under `src/test/java/net/dmulloy2/protocol/wrappers/<phase>/<direction>/`. Do not combine tests for multiple wrappers in a single file.
Every test class must call `BukkitInitialization.initializeAll()` in a `@BeforeAll` method — this bootstraps the Minecraft registry and mocked Bukkit server required for `PacketContainer` and NMS reflection to work.
Cover these scenarios for each wrapper:
* testAllArgsCreate
* Construct a fresh wrapper using the all-args constructor with known test values
* Get the NMS handle packet via `getHandle()`
* Assert that all fields in the NMS handle match the known test values
* testNoArgsCreate
* Construct a fresh wrapper using the no-args constructor
* Get the NMS handle packet via `getHandle()`
* Assert that all fields in the NMS handle are not null
* testModifyExistingPacket
* Create the NMS handle packet directly from its constructor, passing known test values
* Create a packet container from the NMS handle via `PacketContainer.fromPacket()`
* Construct the wrapper using the packet container
* Assert that all fields in the wrapper match the known test values
* Change all fields in the wrapper to new test values
* Verify that the NMS handle packet reflects the new test values
* testWrongPacketTypeThrows
* Create a packet container for a different packet type
* Attempt to construct the wrapper using this incorrect packet container
* Assert that an appropriate exception is thrown (e.g. IllegalArgumentException)
### Appropriate test values
* For numeric fields, use a mix of positive, negative, and zero values (e.g. -5, 0, 3)
* For enum fields, prefer the 2nd or 3rd constant rather than the first, to ensure the wrapper correctly handles non-default enum values
* Additionally, if the enum constant is @Deprecated, do not use it
Here's a good example of a complete test class for `WrappedClientboundSetExperiencePacket`:
```java
class WrappedClientboundSetExperiencePacketTest {
@BeforeAll
static void beforeAll() {
BukkitInitialization.initializeAll();
}
@Test
void testAllArgsCreate() {
WrappedClientboundSetExperiencePacket w = new WrappedClientboundSetExperiencePacket(0.75f, 350, 12);
assertEquals(PacketType.Play.Server.EXPERIENCE, w.getHandle().getType());
ClientboundSetExperiencePacket p = (ClientboundSetExperiencePacket) w.getHandle().getHandle();
assertEquals(0.75f, p.getExperienceProgress(), 1e-4f);
assertEquals(350, p.getTotalExperience());
assertEquals(12, p.getExperienceLevel());
}
@Test
void testNoArgsCreate() {
WrappedClientboundSetExperiencePacket w = new WrappedClientboundSetExperiencePacket();
assertEquals(PacketType.Play.Server.EXPERIENCE, w.getHandle().getType());
ClientboundSetExperiencePacket p = (ClientboundSetExperiencePacket) w.getHandle().getHandle();
assertEquals(0.0f, p.getExperienceProgress(), 1e-4f);
assertEquals(0, p.getTotalExperience());
assertEquals(0, p.getExperienceLevel());
}
@Test
void testModifyExistingPacket() {
ClientboundSetExperiencePacket nmsPacket = new ClientboundSetExperiencePacket(
0.5f, 100, 5
);
PacketContainer container = PacketContainer.fromPacket(nmsPacket);
WrappedClientboundSetExperiencePacket wrapper = new WrappedClientboundSetExperiencePacket(container);
assertEquals(0.5f, wrapper.getExperienceProgress(), 1e-4f);
assertEquals(100, wrapper.getTotalExperience());
assertEquals(5, wrapper.getExperienceLevel());
wrapper.setExperienceProgress(0.25f);
wrapper.setTotalExperience(200);
wrapper.setExperienceLevel(10);
assertEquals(0.25f, nmsPacket.getExperienceProgress(), 1e-4f);
assertEquals(200, nmsPacket.getTotalExperience());
assertEquals(10, nmsPacket.getExperienceLevel());
}
@Test
void testModifyExistingPacket() {
ClientboundSetExperiencePacket nmsPacket = new ClientboundSetExperiencePacket(
0.5f, 100, 5
);
PacketContainer container = PacketContainer.fromPacket(nmsPacket);
WrappedClientboundSetExperiencePacket wrapper = new WrappedClientboundSetExperiencePacket(container);
wrapper.setTotalExperience(200);
assertEquals(0.5f, wrapper.getExperienceProgress(), 1e-4f);
assertEquals(200, wrapper.getTotalExperience());
assertEquals(5, wrapper.getExperienceLevel());
}
@Test
void testWrongPacketTypeThrows() {
assertThrows(IllegalArgumentException.class,
() -> new WrappedClientboundSetExperiencePacket(
new PacketContainer(PacketType.Play.Server.CHAT)));
}
}
```
+57 -11
View File
@@ -14,16 +14,19 @@ lead to more subtle bugs.
* [Spigot Page](https://spigotmc.org/resources/protocollib.1997/)
* [Releases](https://github.com/dmulloy2/ProtocolLib/releases)
* [Dev Build](https://github.com/dmulloy2/ProtocolLib/releases/tag/dev-build)
* [JavaDoc](https://dmulloy2.net/ProtocolLib/javadoc/)
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib/)
* [JavaDoc](https://ci.dmulloy2.net/job/ProtocolLib/javadoc/index.html)
* [Protocol Wiki](https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol)
`ProtocolLib.jar` is the primary Paper plugin. Spigot servers must use the compatibility artifact,
`ProtocolLib-Spigot.jar`. The Maven artifact is the platform-neutral library shared by both distributions.
### Compilation
ProtocolLib is built with [Gradle](https://gradle.org/). If you have it installed, just run
`./gradlew build` in the root project folder. Other gradle targets you may be interested in
include `clean`, `test`, and `shadowJar`. `shadowJar` will create a jar with all dependencies
(ByteBuddy) included.
(ByteBuddy) included for both Paper and Spigot.
### A new API
@@ -66,6 +69,48 @@ dependencies {
}
```
#### Snapshot builds
Snapshot builds are available for testing changes before the next release. To use `5.5.0-SNAPSHOT`,
add the [public repository](https://repo.dmulloy2.net/repository/public/) and update your dependency version.
For Maven:
```xml
<repositories>
<repository>
<id>dmulloy2-public</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.5.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
For Gradle:
```gradle
repositories {
mavenCentral()
maven {
url = uri('https://repo.dmulloy2.net/repository/public/')
}
}
dependencies {
compileOnly 'net.dmulloy2:ProtocolLib:5.5.0-SNAPSHOT'
}
```
#### Accessing the API
Then get a reference to ProtocolManager in onLoad() or onEnable() and you're good to go.
````java
@@ -92,25 +137,26 @@ protocolManager.addPacketListener(new PacketAdapter(
});
````
It's also possible to read and modify the content of these packets. For instance, you can create a global
censor by listening for Packet3Chat events:
It's also possible to read and modify the content of packets, including the ones sent by a client. For
instance, you can censor the text players write on signs by listening for `UPDATE_SIGN` packets:
````java
// Censor
// Censor signs
protocolManager.addPacketListener(new PacketAdapter(
this,
ListenerPriority.NORMAL,
PacketType.Play.Client.CHAT
PacketType.Play.Client.UPDATE_SIGN
) {
@Override
public void onPacketReceiving(PacketEvent event) {
PacketContainer packet = event.getPacket();
String message = packet.getStrings().read(0);
String[] lines = packet.getStringArrays().read(0);
if (message.contains("shit") || message.contains("damn")) {
event.setCancelled(true);
event.getPlayer().sendMessage("Bad manners!");
for (int i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace("shit", "****").replace("damn", "****");
}
packet.getStringArrays().write(0, lines);
}
});
````
+20 -53
View File
@@ -1,10 +1,7 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
`maven-publish`
signing
id("com.gradleup.shadow") version "9.4.0"
id("io.github.patrick.remapper") version "1.4.3"
id("com.vanniktech.maven.publish") version "0.36.0"
}
@@ -12,7 +9,7 @@ plugins {
group = "net.dmulloy2"
description = "Provides access to the Minecraft protocol"
val mcVersion = "26.2"
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val isJitPack = System.getenv("JITPACK")?.equals("true", ignoreCase = true) ?: false
val commitHash = System.getenv("COMMIT_SHA") ?: ""
@@ -46,20 +43,11 @@ repositories {
dependencies {
implementation("net.bytebuddy:byte-buddy:1.18.2")
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")//:remapped-mojang")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-common:4.2.8.Final")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")//:remapped-mojang")
testImplementation("net.kyori:adventure-text-serializer-gson:4.25.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.25.0")
compileOnly("commons-lang:commons-lang:2.6")
}
java {
@@ -68,49 +56,22 @@ java {
}
}
tasks {
processResources {
val fullVersion = if (isSnapshot && isCI) "${version}-${commitHash.take(7)}" else version
eachFile {
expand("version" to fullVersion)
}
}
sourceSets {
test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
java.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
}
}
tasks {
test {
dependsOn(":paper:test")
}
shadowJar {
dependencies {
include(dependency("net.bytebuddy:byte-buddy:.*"))
}
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes(
"paperweight-mappings-namespace" to "mojang"
)
}
archiveFileName = "ProtocolLib.jar"
check {
dependsOn(":paper:check", ":spigot:check")
}
/*remap {
dependsOn("shadowJar")
inputTask.set(getByName<ShadowJar>("shadowJar"))
version.set(mcVersion)
action.set(RemapTask.Action.MOJANG_TO_SPIGOT)
}
assemble {
dependsOn("remap")
}*/
javadoc {
options.encoding = "UTF-8"
}
@@ -121,6 +82,12 @@ tasks {
}
}
tasks.register("shadowJar") {
group = "build"
description = "Builds the Paper and Spigot plugin distributions."
dependsOn(":paper:shadowJar", ":spigot:shadowJar")
}
mavenPublishing {
publishToMavenCentral()
if (!isSnapshot && !isJitPack) {
+1
View File
@@ -1,4 +1,5 @@
version=5.5.0-SNAPSHOT
mcVersion=26.3
mavenCentralUsername=
mavenCentralPassword=
signing.keyId=
+104
View File
@@ -0,0 +1,104 @@
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
id("com.gradleup.shadow") version "9.4.0"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
group = "net.dmulloy2"
version = rootProject.version
val mcVersion: String by project
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.codemc.io/repository/nms/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://libraries.minecraft.net/") {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation(project(":"))
paperweight.paperDevBundle("$mcVersion.build.+")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("commons-lang:commons-lang:2.6")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-common:4.2.8.Final")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("net.kyori:adventure-text-serializer-gson:4.25.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.25.0")
testImplementation("commons-lang:commons-lang:2.6")
}
paperweight.reobfArtifactConfiguration =
io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION
configurations.matching { it.name == "compileClasspath" || it.name == "runtimeClasspath" || it.name == "testCompileClasspath" || it.name == "testRuntimeClasspath" }.configureEach {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
sourceSets {
test {
java.srcDir(rootProject.file("src/test/java"))
resources.srcDir(rootProject.file("src/test/resources"))
}
}
tasks {
processResources {
val fullVersion = if (isSnapshot && isCI) "${version}-${commitHash.take(7)}" else version
eachFile {
expand("version" to fullVersion)
}
}
test {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
}
compileJava {
options.release.set(17)
}
shadowJar {
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
archiveFileName.set("ProtocolLib.jar")
}
}
@@ -0,0 +1,105 @@
package com.comphenix.protocol.paper;
import java.util.List;
import java.util.function.Consumer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.internal.PlatformProvider;
import io.netty.channel.Channel;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.network.ChannelInitializeListenerHolder;
import net.kyori.adventure.key.Key;
public final class PaperPlatformProvider implements PlatformProvider {
public static final Key CHANNEL_INITIALIZER_KEY = Key.key("protocollib", "channel-initializer");
@Override
public boolean hasEarlyChannelInitialization() {
return true;
}
@Override
public Runnable registerChannelInitializer(Consumer<Channel> channelInitializer) {
ChannelInitializeListenerHolder.addListener(CHANNEL_INITIALIZER_KEY, channelInitializer::accept);
return () -> ChannelInitializeListenerHolder.removeListener(CHANNEL_INITIALIZER_KEY);
}
@Override
public void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor) {
CommandMetadata metadata = getMetadata(name);
Command command = new RegisteredCommand(name, metadata);
plugin.registerCommand(
name,
metadata.description(),
metadata.aliases(),
new RegisteredBasicCommand(name, metadata, command, executor));
}
private static CommandMetadata getMetadata(String name) {
return switch (name) {
case "protocol" -> new CommandMetadata(
"Performs administrative tasks regarding ProtocolLib.",
"/<command> config|check|update|timings|listeners|version|dump",
"protocol.admin",
List.of());
case "packet" -> new CommandMetadata(
"Add or remove a simple packet listener.",
"/<command> add|remove|names client|server [ID start]-[ID stop] [detailed]",
"protocol.admin",
List.of());
case "filter" -> new CommandMetadata(
"Add or remove programmable filters to the packet listeners.",
"/<command> add|remove name [ID start]-[ID stop]",
"protocol.admin",
List.of("packet_filter"));
case "packetlog" -> new CommandMetadata(
"Logs hex representations of packets to a file or console",
"/<command> <protocol> <sender> <packet> [location]",
"protocol.admin",
List.of());
default -> throw new IllegalArgumentException("Unknown ProtocolLib command " + name);
};
}
private record CommandMetadata(String description, String usage, String permission, List<String> aliases) {
}
private record RegisteredBasicCommand(
String name,
CommandMetadata metadata,
Command command,
CommandExecutor executor) implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] arguments) {
if (!this.executor.onCommand(source.getSender(), this.command, this.name, arguments)) {
source.getSender().sendMessage(this.metadata.usage().replace("<command>", this.name));
}
}
@Override
public String permission() {
return this.metadata.permission();
}
}
private static final class RegisteredCommand extends Command {
private RegisteredCommand(String name, CommandMetadata metadata) {
super(name, metadata.description(), metadata.usage(), metadata.aliases());
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
throw new UnsupportedOperationException();
}
}
}
@@ -0,0 +1 @@
com.comphenix.protocol.paper.PaperPlatformProvider
+22
View File
@@ -0,0 +1,22 @@
name: ProtocolLib
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2]
main: com.comphenix.protocol.ProtocolLib
load: STARTUP
api-version: "26.3"
folia-supported: true
permissions:
protocol.*:
description: Gives access to everything.
children:
protocol.admin: true
protocol.info: true
protocol.admin:
description: Able to initiate the update process and configure debug mode.
default: op
protocol.info:
description: Can read update notifications and error reports.
default: op
+2
View File
@@ -3,3 +3,5 @@
*/
rootProject.name = 'ProtocolLib'
include 'paper', 'spigot'
+79
View File
@@ -0,0 +1,79 @@
import org.gradle.api.attributes.java.TargetJvmVersion
plugins {
`java-library`
id("com.gradleup.shadow") version "9.4.0"
}
group = "net.dmulloy2"
version = rootProject.version
val mcVersion: String by project
repositories {
mavenLocal()
mavenCentral()
maven("https://repo.codemc.io/repository/nms/")
maven("https://hub.spigotmc.org/nexus/content/groups/public/")
maven("https://libraries.minecraft.net/") {
metadataSources {
mavenPom()
artifact()
ignoreGradleMetadataRedirection()
}
}
}
dependencies {
implementation(project(":"))
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("io.netty:netty-all:4.2.8.Final")
compileOnly("net.kyori:adventure-text-serializer-gson:4.25.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.1")
testImplementation("org.mockito:mockito-core:5.21.0")
testImplementation("io.netty:netty-transport:4.2.8.Final")
testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")
}
configurations.matching { it.name == "compileClasspath" || it.name == "runtimeClasspath" || it.name == "testCompileClasspath" || it.name == "testRuntimeClasspath" }.configureEach {
attributes {
attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 25)
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
tasks {
processResources {
eachFile {
expand("version" to project.version)
}
}
test {
useJUnitPlatform()
}
compileJava {
options.release.set(17)
}
shadowJar {
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
archiveFileName.set("ProtocolLib-Spigot.jar")
}
}
@@ -0,0 +1,34 @@
package com.comphenix.protocol.spigot;
import java.util.function.Consumer;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.internal.PlatformProvider;
import io.netty.channel.Channel;
public final class SpigotPlatformProvider implements PlatformProvider {
@Override
public boolean hasEarlyChannelInitialization() {
return false;
}
@Override
public Runnable registerChannelInitializer(Consumer<Channel> channelInitializer) {
throw new UnsupportedOperationException("Early channel initialization is unavailable");
}
@Override
public void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor) {
PluginCommand command = plugin.getCommand(name);
if (command == null) {
throw new IllegalStateException("plugin.yml might be corrupt.");
}
command.setExecutor(executor);
}
}
@@ -0,0 +1 @@
com.comphenix.protocol.spigot.SpigotPlatformProvider
@@ -1,7 +1,7 @@
name: ProtocolLib
version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2, comphenix]
authors: [dmulloy2]
main: com.comphenix.protocol.ProtocolLib
load: STARTUP
@@ -44,4 +44,4 @@ permissions:
default: op
protocol.info:
description: Can read update notifications and error reports.
default: op
default: op
@@ -0,0 +1,26 @@
package com.comphenix.protocol;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.spigot.SpigotPlatformProvider;
class PlatformCommandRegistrarTest {
@Test
void registersLegacyExecutorFromPluginDescriptor() {
ProtocolLib plugin = mock(ProtocolLib.class);
CommandExecutor executor = mock(CommandExecutor.class);
PluginCommand command = mock(PluginCommand.class);
when(plugin.getCommand("protocol")).thenReturn(command);
new SpigotPlatformProvider().registerCommand(plugin, "protocol", executor);
verify(command).setExecutor(executor);
}
}
@@ -0,0 +1,17 @@
package com.comphenix.protocol.injector.netty.manager;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.internal.PlatformProvider;
class PlatformChannelInitializerTest {
@Test
void reportsPaperInitializerUnavailable() {
PlatformProvider provider = PlatformProvider.get();
assertInstanceOf(com.comphenix.protocol.spigot.SpigotPlatformProvider.class, provider);
assertFalse(provider.hasEarlyChannelInitialization());
}
}
@@ -120,25 +120,9 @@ class CommandProtocol extends CommandBase {
return;
}
// Perform on an async thread
Runnable notify = new Runnable() {
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
updateFinished();
}
};
// Top-level class instead of anonymous inner class so that PaperPluginClassLoader
// can resolve the .class entry when BukkitScheduler dispatches it on the main thread.
Runnable notify = new UpdateNotifyRunnable(sender, command, updater, config, plugin, this);
updater.start(type);
updater.addListener(notify);
}
@@ -144,110 +144,113 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x22, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x23, "EntityPositionSync");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x24, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x25, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x26, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType GAME_RULE_VALUES = new PacketType(PROTOCOL, SENDER, 0x27, "GameRuleValues");
public static final PacketType GAME_TEST_HIGHLIGHT_POS = new PacketType(PROTOCOL, SENDER, 0x28, "GameTestHighlightPos");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x29, "MountScreenOpen", "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2A, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x2B, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x2C, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x2D, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x2E, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x2F, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x30, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x31, "Login", "SPacketJoinGame");
public static final PacketType LOW_DISK_SPACE_WARNING = new PacketType(PROTOCOL, SENDER, 0x32, "LowDiskSpaceWarning");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x33, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x34, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x35, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x36, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x37, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x38, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x39, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x3A, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x3B, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x3C, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x3D, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x3E, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x3F, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x41, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x42, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x43, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x44, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x45, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x46, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x47, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x48, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x49, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x4A, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x4B, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x4C, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x4D, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x4E, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x4F, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x50, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x51, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x52, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x53, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x54, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x55, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x56, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x57, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x58, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x59, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x5A, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x5B, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5C, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x5D, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x5E, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5F, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x60, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x61, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x62, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x63, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x64, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x65, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x66, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x67, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x68, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x69, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x6A, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x6B, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x6C, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x6D, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x6E, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x6F, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x70, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x71, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x72, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x73, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x74, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x75, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x76, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x77, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x78, "StoreCookie");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x79, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x7A, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x7B, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x7C, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x7D, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x7E, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x7F, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x80, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x81, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x82, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x83, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x84, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x85, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x86, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x87, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x88, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x89, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x8A, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8B, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8C, "ShowDialog");
public static final PacketType ADD_TRANSIENT_BLOCK = new PacketType(PROTOCOL, SENDER, 0x25, MinecraftVersion.v26_3, "AddTransientBlock");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x26, MinecraftVersion.v26_3, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x27, MinecraftVersion.v26_3, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType GAME_RULE_VALUES = new PacketType(PROTOCOL, SENDER, 0x28, MinecraftVersion.v26_3, "GameRuleValues");
public static final PacketType GAME_TEST_HIGHLIGHT_POS = new PacketType(PROTOCOL, SENDER, 0x29, MinecraftVersion.v26_3, "GameTestHighlightPos");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x2A, MinecraftVersion.v26_3, "MountScreenOpen", "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x2B, MinecraftVersion.v26_3, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x2C, MinecraftVersion.v26_3, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x2D, MinecraftVersion.v26_3, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x2E, MinecraftVersion.v26_3, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x2F, MinecraftVersion.v26_3, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x30, MinecraftVersion.v26_3, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x31, MinecraftVersion.v26_3, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x32, MinecraftVersion.v26_3, "Login", "SPacketJoinGame");
public static final PacketType LOW_DISK_SPACE_WARNING = new PacketType(PROTOCOL, SENDER, 0x33, MinecraftVersion.v26_3, "LowDiskSpaceWarning");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x34, MinecraftVersion.v26_3, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x35, MinecraftVersion.v26_3, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x36, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x37, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x38, MinecraftVersion.v26_3, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x39, MinecraftVersion.v26_3, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x3A, MinecraftVersion.v26_3, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x3B, MinecraftVersion.v26_3, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x3C, MinecraftVersion.v26_3, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x3D, MinecraftVersion.v26_3, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x3E, MinecraftVersion.v26_3, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x3F, MinecraftVersion.v26_3, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x40, MinecraftVersion.v26_3, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x41, MinecraftVersion.v26_3, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x42, MinecraftVersion.v26_3, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x43, MinecraftVersion.v26_3, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x44, MinecraftVersion.v26_3, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x45, MinecraftVersion.v26_3, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x46, MinecraftVersion.v26_3, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x47, MinecraftVersion.v26_3, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x48, MinecraftVersion.v26_3, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x49, MinecraftVersion.v26_3, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x4A, MinecraftVersion.v26_3, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x4B, MinecraftVersion.v26_3, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x4C, MinecraftVersion.v26_3, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x4D, MinecraftVersion.v26_3, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x4E, MinecraftVersion.v26_3, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x4F, MinecraftVersion.v26_3, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x50, MinecraftVersion.v26_3, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x51, MinecraftVersion.v26_3, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x52, MinecraftVersion.v26_3, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType POST_EFFECTS = new PacketType(PROTOCOL, SENDER, 0x53, MinecraftVersion.v26_3, "PostEffects");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x54, MinecraftVersion.v26_3, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x55, MinecraftVersion.v26_3, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x56, MinecraftVersion.v26_3, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x57, MinecraftVersion.v26_3, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x58, MinecraftVersion.v26_3, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x59, MinecraftVersion.v26_3, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x5A, MinecraftVersion.v26_3, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x5B, MinecraftVersion.v26_3, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x5C, MinecraftVersion.v26_3, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x5D, MinecraftVersion.v26_3, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x5E, MinecraftVersion.v26_3, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x5F, MinecraftVersion.v26_3, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x60, MinecraftVersion.v26_3, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x61, MinecraftVersion.v26_3, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x62, MinecraftVersion.v26_3, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x63, MinecraftVersion.v26_3, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x64, MinecraftVersion.v26_3, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x65, MinecraftVersion.v26_3, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x66, MinecraftVersion.v26_3, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x67, MinecraftVersion.v26_3, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x68, MinecraftVersion.v26_3, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x69, MinecraftVersion.v26_3, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x6A, MinecraftVersion.v26_3, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x6B, MinecraftVersion.v26_3, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x6C, MinecraftVersion.v26_3, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x6D, MinecraftVersion.v26_3, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x6E, MinecraftVersion.v26_3, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x6F, MinecraftVersion.v26_3, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x70, MinecraftVersion.v26_3, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x71, MinecraftVersion.v26_3, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x72, MinecraftVersion.v26_3, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x73, MinecraftVersion.v26_3, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x74, MinecraftVersion.v26_3, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x75, MinecraftVersion.v26_3, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x76, MinecraftVersion.v26_3, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x77, MinecraftVersion.v26_3, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x78, MinecraftVersion.v26_3, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x79, MinecraftVersion.v26_3, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x7A, MinecraftVersion.v26_3, "StoreCookie");
public static final PacketType SWING_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x7B, MinecraftVersion.v26_3, "SwingAnimation");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x7C, MinecraftVersion.v26_3, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x7D, MinecraftVersion.v26_3, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x7E, MinecraftVersion.v26_3, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x7F, MinecraftVersion.v26_3, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x80, MinecraftVersion.v26_3, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x81, MinecraftVersion.v26_3, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x82, MinecraftVersion.v26_3, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x83, MinecraftVersion.v26_3, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x84, MinecraftVersion.v26_3, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x85, MinecraftVersion.v26_3, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x86, MinecraftVersion.v26_3, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x87, MinecraftVersion.v26_3, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x88, MinecraftVersion.v26_3, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x89, MinecraftVersion.v26_3, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x8A, MinecraftVersion.v26_3, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x8B, MinecraftVersion.v26_3, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x8C, MinecraftVersion.v26_3, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x8D, MinecraftVersion.v26_3, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8E, MinecraftVersion.v26_3, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x8F, MinecraftVersion.v26_3, "ShowDialog");
/**
* @deprecated Removed in 1.9
@@ -490,24 +493,24 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2C, "PlayerLoaded");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2D, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2E, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x2F, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x30, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x31, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x32, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x33, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x34, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x35, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x36, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x37, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x38, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_GAME_RULE = new PacketType(PROTOCOL, SENDER, 0x39, "SetGameRule");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x3A, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x3B, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3C, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3D, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType SPECTATE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x3E, "SpectatorAction", "SpectateEntity");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x3F, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType PUNCH = new PacketType(PROTOCOL, SENDER, 0x2E, MinecraftVersion.v26_3, "Punch");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2F, MinecraftVersion.v26_3, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x30, MinecraftVersion.v26_3, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x31, MinecraftVersion.v26_3, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x32, MinecraftVersion.v26_3, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x33, MinecraftVersion.v26_3, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x34, MinecraftVersion.v26_3, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x35, MinecraftVersion.v26_3, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x36, MinecraftVersion.v26_3, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x37, MinecraftVersion.v26_3, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x38, MinecraftVersion.v26_3, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x39, MinecraftVersion.v26_3, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_GAME_RULE = new PacketType(PROTOCOL, SENDER, 0x3A, MinecraftVersion.v26_3, "SetGameRule");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x3B, MinecraftVersion.v26_3, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x3C, MinecraftVersion.v26_3, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3D, MinecraftVersion.v26_3, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3E, MinecraftVersion.v26_3, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType SPECTATE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x3F, MinecraftVersion.v26_3, "SpectatorAction", "SpectateEntity");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x40, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType TEST_INSTANCE_BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x41, "TestInstanceBlockAction");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x42, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
@@ -538,6 +541,12 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
/**
* @deprecated Removed in 26.3
*/
@Deprecated
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 252, "Swing", "ArmAnimation", "CPacketAnimation");
/**
* @deprecated Renamed to USE_ITEM_ON
*/
@@ -719,16 +728,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType REGISTRY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "RegistryData");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x08, "ResourcePackPop");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x09, "ResourcePackPush");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0A, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0B, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0C, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x0F, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x10, "ServerLinks");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x11, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x12, "ShowDialog");
public static final PacketType CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x13, "CodeOfConduct");
public static final PacketType POST_EFFECTS = new PacketType(PROTOCOL, SENDER, 0x0A, "PostEffects");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x0B, "StoreCookie");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x0C, "Transfer");
public static final PacketType UPDATE_ENABLED_FEATURES = new PacketType(PROTOCOL, SENDER, 0x0D, "UpdateEnabledFeatures");
public static final PacketType UPDATE_TAGS = new PacketType(PROTOCOL, SENDER, 0x0E, "UpdateTags");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x0F, "ClientboundSelectKnownPacks");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x10, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x11, "ServerLinks");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x12, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x13, "ShowDialog");
public static final PacketType CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x14, "CodeOfConduct");
/**
* @deprecated Removed in 1.20.4: replaced with new packets for removing and sending resource packs
@@ -49,10 +49,11 @@ import java.util.logging.Logger;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import com.comphenix.protocol.internal.PlatformProvider;
/**
* The main entry point for ProtocolLib.
*
@@ -444,14 +445,7 @@ public class ProtocolLib extends JavaPlugin {
return;
}
PluginCommand command = this.getCommand(name);
// Try to load the command
if (command != null) {
command.setExecutor(executor);
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
PlatformProvider.get().registerCommand(this, name, executor);
} catch (RuntimeException e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
@@ -35,12 +35,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "26.2";
public static final String MAXIMUM_MINECRAFT_VERSION = "26.3";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (26.2) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (26.3) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2026-06-16";
public static final String MINECRAFT_LAST_RELEASE_DATE = "2026-09-15";
private static Plugin plugin;
private static ProtocolConfig config;
@@ -0,0 +1,69 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.updater.Updater;
/**
* Callback invoked on the main thread after an asynchronous update check completes.
* <p>
* Extracted as a top-level class (instead of an anonymous inner class) so that
* {@code PaperPluginClassLoader} can always resolve its {@code .class} entry when
* {@link org.bukkit.scheduler.BukkitScheduler#runTask} dispatches it back onto the
* main server thread — the same pattern used by {@code SpigotUpdateRunnable}.
*/
final class UpdateNotifyRunnable implements Runnable {
private final CommandSender sender;
private final boolean command;
private final Updater updater;
private final ProtocolConfig config;
private final Plugin plugin;
private final CommandProtocol owner;
UpdateNotifyRunnable(CommandSender sender, boolean command,
Updater updater, ProtocolConfig config,
Plugin plugin, CommandProtocol owner) {
this.sender = sender;
this.command = command;
this.updater = updater;
this.config = config;
this.plugin = plugin;
this.owner = owner;
}
@Override
public void run() {
if (command) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
String remoteVersion = updater.getRemoteVersion();
if (remoteVersion != null) {
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
}
} else if (updater.shouldNotify() || config.isDebug()) {
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
}
updater.removeListener(this);
owner.updateFinished();
}
}
@@ -309,12 +309,10 @@ public class AsyncFilterManager implements AsynchronousManager {
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
// The player is only be null when they're logged out,
// so this should be a pretty safe check
Player player = syncPacket.getPlayer();
if (player != null) {
PacketSendingQueue sendingQueue = getSendingQueue(syncPacket);
if (sendingQueue != null) {
// Start the process
getSendingQueue(syncPacket).enqueue(newEvent);
sendingQueue.enqueue(newEvent);
// We know this is occurring on the main thread, so pass TRUE
getProcessingQueue(syncPacket).enqueue(newEvent, true);
@@ -323,12 +321,12 @@ public class AsyncFilterManager implements AsynchronousManager {
@Override
public Set<PacketType> getReceivingTypes() {
return serverProcessingQueue.keySet();
return clientProcessingQueue.keySet();
}
@Override
public Set<PacketType> getSendingTypes() {
return clientProcessingQueue.keySet();
return serverProcessingQueue.keySet();
}
/**
@@ -134,14 +134,18 @@ class PlayerSendingHandler {
* @return The server or client sending queue the packet belongs to.
*/
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
Player player = packet.getPlayer();
if (player == null)
return null;
QueueContainer queues = playerSendingQueues.get(player);
// Safe concurrent initialization
if (queues == null && createNew) {
final QueueContainer newContainer = new QueueContainer();
// Attempt to map the queue
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
queues = playerSendingQueues.putIfAbsent(player, newContainer);
if (queues == null) {
queues = newContainer;
@@ -58,6 +58,8 @@ public class IntegerMap<T> {
* @return The old associated value, or NULL.
*/
public T remove(int key) {
if (key < 0 || key >= array.length) return null;
T old = array[key];
array[key] = null;
@@ -190,7 +190,8 @@ public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> imp
Preconditions.checkNotNull(player, "player cannot be null");
Object key = keyMethod.apply(player);
Preconditions.checkState(key != null, "Unable to derive a key for the player.");
keyLookup.put(key, player);
return key;
}
@@ -56,6 +56,7 @@ import com.comphenix.protocol.wrappers.WrappedMessageSignature;
import com.comphenix.protocol.wrappers.WrappedNumberFormat;
import com.comphenix.protocol.wrappers.WrappedParticle;
import com.comphenix.protocol.wrappers.WrappedPositionMoveRotation;
import com.comphenix.protocol.wrappers.WrappedPositionPath;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.WrappedRegistrable;
@@ -65,8 +66,8 @@ import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedStatistic;
import com.comphenix.protocol.wrappers.WrappedTeamParameters;
import com.comphenix.protocol.wrappers.WrappedVecDelta;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.WrappedWeightedList;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
@@ -420,16 +421,6 @@ public abstract class AbstractStructure {
BukkitConverters.getVectorConverter());
}
/**
* Retrieves a read/write structure for PositionMoveRotation fields.
* @return A modifier for PositionMoveRotation fields.
*/
public StructureModifier<WrappedPositionMoveRotation> getPositionMoveRotations() {
return structureModifier.withType(
MinecraftReflection.getMinecraftClass("world.entity.PositionMoveRotation"),
WrappedPositionMoveRotation.getConverter());
}
/**
* Retrieves a read/write structure for collections of attribute snapshots.
* <p>
@@ -814,20 +805,6 @@ public abstract class AbstractStructure {
);
}
/**
* Retrieve a read/write structure for WeightedList fields.
*
* @param elementConverter converter for the element type inside each weighted entry
* @param <T> the Bukkit-side element type
* @return A modifier for WeightedList fields.
*/
public <T> StructureModifier<WrappedWeightedList<T>> getWeightedLists(
EquivalentConverter<T> elementConverter) {
return structureModifier.withType(
WrappedWeightedList.getNmsClass(),
WrappedWeightedList.getConverter(elementConverter));
}
/**
* Retrieve a read/write structure for the MobEffectList class in 1.9.
* @return A modifier for MobEffectList fields.
@@ -843,15 +820,6 @@ public abstract class AbstractStructure {
BukkitConverters.getEffectTypeConverter());
}
/**
* Retrieve a read/write structure for Bukkit {@code DamageType} values
* stored in NMS {@code Holder<DamageType>} fields.
* @return A modifier for Bukkit DamageType values.
*/
public StructureModifier<org.bukkit.damage.DamageType> getDamageTypes() {
return getHolders(MinecraftReflection.getDamageTypeClass(), BukkitConverters.getDamageTypeConverter());
}
/**
* Retrieve a read/write structure for the SoundCategory enum in 1.9.
* @return A modifier for SoundCategory enum fields.
@@ -1198,9 +1166,35 @@ public abstract class AbstractStructure {
}
/**
* Since 26.3 the signature fields are optional, an absent signature is read and written as {@code null}.
*
* @return read/writer structure direct access to unsalted signature data for example in chat message (since 1.19.3)
*/
public StructureModifier<WrappedMessageSignature> getMessageSignatures() {
if (MinecraftVersion.v26_3.atOrAbove()) {
EquivalentConverter<WrappedMessageSignature> converter = BukkitConverters.getWrappedMessageSignatureConverter();
return structureModifier.withParamType(
Optional.class,
new EquivalentConverter<WrappedMessageSignature>() {
@Override
public Object getGeneric(WrappedMessageSignature specific) {
return Optional.ofNullable(converter.getGeneric(specific));
}
@Override
public WrappedMessageSignature getSpecific(Object generic) {
return generic == null ? null : ((Optional<?>) generic).map(converter::getSpecific).orElse(null);
}
@Override
public Class<WrappedMessageSignature> getSpecificType() {
return WrappedMessageSignature.class;
}
},
MinecraftReflection.getMessageSignatureClass()
);
}
return structureModifier.withType(
MinecraftReflection.getMessageSignatureClass(),
BukkitConverters.getWrappedMessageSignatureConverter()
@@ -1333,7 +1327,7 @@ public abstract class AbstractStructure {
/**
* Retrieve a read/write structure for {@link WrappedPositionMoveRotation} (available since Minecraft 1.21.2).
* <p>
* This is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC}.
* This is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC} (before 26.3).
*
* @return A modifier for PositionMoveRotation fields.
*/
@@ -1344,6 +1338,34 @@ public abstract class AbstractStructure {
);
}
/**
* Retrieve a read/write structure for {@link WrappedVecDelta} (available since Minecraft 26.3).
* <p>
* This is used in {@code REL_ENTITY_MOVE} and {@code REL_ENTITY_MOVE_LOOK}.
*
* @return A modifier for VecDelta fields.
*/
public StructureModifier<WrappedVecDelta> getVecDeltas() {
return structureModifier.withType(
MinecraftReflection.getVecDeltaClass().orElse(void.class),
WrappedVecDelta.getConverter()
);
}
/**
* Retrieve a read/write structure for {@link WrappedPositionPath} (available since Minecraft 26.3).
* <p>
* This is used in {@code ENTITY_POSITION_SYNC}.
*
* @return A modifier for PositionPath fields.
*/
public StructureModifier<WrappedPositionPath> getPositionPaths() {
return structureModifier.withType(
MinecraftReflection.getPositionPathClass().orElse(void.class),
WrappedPositionPath.getConverter()
);
}
/**
* Represents an equivalent converter for ItemStack arrays.
* @author Kristian
@@ -391,6 +391,8 @@ public class PacketEvent extends EventObject implements Cancellable {
* <li>getPlayer</li>
* <li>getAddress</li>
* <li>getServer</li>
* <li>getName, after the login start packet</li>
* <li>getUniqueId, when supplied by the login protocol</li>
* <li>chat</li>
* <li>sendMessage</li>
* <li>kickPlayer</li>
@@ -22,8 +22,10 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
@@ -68,7 +70,7 @@ import org.jetbrains.annotations.Nullable;
*
* @author Kristian
*/
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
class SerializedOfflinePlayer implements Serializable {
/**
* Generated by Eclipse.
@@ -122,22 +124,18 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
@Override
public boolean isOp() {
return operator;
}
@Override
public void setOp(boolean operator) {
this.operator = operator;
}
@Override
public Map<String, Object> serialize() {
throw new UnsupportedOperationException();
}
@Override
public Location getBedSpawnLocation() {
return bedSpawnLocation;
}
@@ -152,12 +150,10 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return lastSeen;
}
@Override
public Location getRespawnLocation() {
return null;
}
@Override
public Location getLocation() {
return null;
}
@@ -221,42 +217,34 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
public void setStatistic(Statistic statistic, EntityType entityType, int i) {
}
@Override
public Location getLastDeathLocation() {
return null;
}
@Override
public long getFirstPlayed() {
return firstPlayed;
}
@Override
public long getLastPlayed() {
return lastPlayed;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public @NotNull PlayerProfile getPlayerProfile() {
return null;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasPlayedBefore() {
return playedBefore;
}
@Override
public boolean isBanned() {
return banned;
}
@@ -283,17 +271,14 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
this.banned = banned;
}
@Override
public boolean isOnline() {
return online;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
}
@Override
public void setWhitelisted(boolean whitelisted) {
this.whitelisted = whitelisted;
}
@@ -330,7 +315,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
@Override
public Player getPlayer() {
try {
// Try to get the real player underneath
@@ -349,7 +333,7 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
*/
public Player getProxyPlayer() {
try {
return (Player) proxyPlayerConstructor.newInstance(this);
return (Player) proxyPlayerConstructor.newInstance(this.createOfflinePlayerProxy());
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
@@ -359,6 +343,38 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
}
}
private OfflinePlayer createOfflinePlayerProxy() {
return (OfflinePlayer) Proxy.newProxyInstance(
OfflinePlayer.class.getClassLoader(),
new Class<?>[]{OfflinePlayer.class},
(proxy, method, arguments) -> {
if (method.getDeclaringClass() == Object.class) {
return switch (method.getName()) {
case "equals" -> proxy == arguments[0];
case "hashCode" -> System.identityHashCode(proxy);
case "toString" -> "SerializedOfflinePlayer[" + this.name + "]";
default -> throw new UnsupportedOperationException("Unsupported Object method " + method);
};
}
try {
Method target = SerializedOfflinePlayer.class.getMethod(
method.getName(),
method.getParameterTypes());
return target.invoke(this, arguments);
} catch (NoSuchMethodException exception) {
if (method.isDefault()) {
return InvocationHandler.invokeDefault(proxy, method, arguments);
}
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.",
exception);
} catch (InvocationTargetException exception) {
throw exception.getCause();
}
});
}
private static Constructor<? extends Player> setupProxyPlayerConstructor() {
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
final String[] methodNames = new String[offlinePlayerMethods.length];
@@ -1,93 +0,0 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.Tuple;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
public class EquivalentConstructor {
private final PacketType packetType;
private final List<Tuple<Class<?>, Object>> converters = new ArrayList<>();
private volatile ConstructorAccessor constructorAccessor;
public EquivalentConstructor(PacketType packetType) {
this.packetType = packetType;
}
public EquivalentConstructor withParam(Class<?> param, EquivalentConverter<?> converter) {
converters.add(new Tuple<>(param, converter));
return this;
}
public EquivalentConstructor withParam(Class<?> param, PacketConstructor.Unwrapper unwrapper) {
converters.add(new Tuple<>(param, unwrapper));
return this;
}
public EquivalentConstructor withParam(Class<?> param) {
converters.add(new Tuple<>(param, null));
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public Object create(Object... args) {
if (args.length != converters.size()) {
throw new IllegalArgumentException("Expected " + converters.size() + " args, got " + args.length);
}
ConstructorAccessor accessor = getConstructorAccessor();
Object[] convertedArgs = new Object[converters.size()];
int i = 0;
for (Tuple<Class<?>, Object> entry : converters) {
Object rawConverter = entry.second();
if (rawConverter == null) {
convertedArgs[i] = args[i];
} else if (rawConverter instanceof EquivalentConverter converter) {
convertedArgs[i] = converter.getGeneric(args[i]);
} else if (rawConverter instanceof PacketConstructor.Unwrapper unwrapper) {
convertedArgs[i] = unwrapper.unwrapItem(args[i]);
} else {
throw new IllegalStateException("Invalid converter type: " + rawConverter.getClass());
}
i++;
}
return accessor.invoke(convertedArgs);
}
private ConstructorAccessor getConstructorAccessor() {
ConstructorAccessor accessor = constructorAccessor;
if (accessor == null) {
synchronized (this) {
accessor = constructorAccessor;
if (accessor != null) {
return accessor;
}
constructorAccessor = accessor = createConstructorAccessor();
}
}
return accessor;
}
private ConstructorAccessor createConstructorAccessor() {
Class<?>[] params = new Class<?>[converters.size()];
for (int i = 0; i < converters.size(); i++) {
params[i] = converters.get(i).first();
}
Constructor<?> ctor = FuzzyReflection.fromClass(packetType.getPacketClass(), true)
.getConstructor(FuzzyMethodContract.newBuilder().parameterExactArray(params).build());
return Accessors.getConstructorAccessor(ctor);
}
}
@@ -426,14 +426,20 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
this.injected = true;
this.networkManagerInjector.inject();
// Paper disables reconfiguration when any PlayerLoginEvent listener is registered.
if (!this.networkManagerInjector.isUsingPaperChannelInitializer()) {
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
}, plugin);
}
// all listeners we need, this is a bit messy, but it makes the job correctly
manager.registerEvents(new Listener() {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
@@ -35,6 +35,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.cloning.ImmutableDetector;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
@@ -82,15 +83,26 @@ public class StructureCache {
return newInstance(packetClass);
}
// determineBestCreator can re-enter this cache (legacy 1.8 packet registry), which
// ConcurrentHashMap.computeIfAbsent rejects on Java 9+, so use get/put instead.
private static Optional<Supplier<Object>> creatorFor(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.get(clazz);
if (creator == null) {
creator = Optional.ofNullable(determineBestCreator(clazz));
Optional<Supplier<Object>> existing = CACHED_INSTANCE_CREATORS.putIfAbsent(clazz, creator);
if (existing != null) {
creator = existing;
}
}
return creator;
}
public static boolean canCreateInstance(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, x ->
Optional.ofNullable(determineBestCreator(clazz)));
return creator.isPresent();
return creatorFor(clazz).isPresent();
}
public static Object newInstance(Class<?> clazz) {
Optional<Supplier<Object>> creator = CACHED_INSTANCE_CREATORS.computeIfAbsent(clazz, x ->
Optional.ofNullable(determineBestCreator(clazz)));
Optional<Supplier<Object>> creator = creatorFor(clazz);
if (!creator.isPresent()) {
throw new IllegalArgumentException("Cannot create instance of " + clazz);
}
@@ -98,15 +110,21 @@ public class StructureCache {
}
static Supplier<Object> determineBestCreator(Class<?> clazz) {
// certain packets are singletons which can't really be created
if (MinecraftReflection.isPacketClass(clazz)) {
boolean packetClass = MinecraftReflection.isPacketClass(clazz);
boolean immutableClass = ImmutableDetector.isImmutable(clazz);
// Certain packets are singletons which can't really be created. Immutable classes may also expose multiple
// constant instances as an enum replacement, which should be reused instead of invoking their constructors.
if (packetClass || immutableClass) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(clazz, false);
List<Field> singletons = fuzzy.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(clazz)
.requireModifier(Modifier.STATIC)
.requireModifier(Modifier.PUBLIC)
.build());
if (singletons.size() == 1) {
// Preserve the existing packet singleton behavior.
if (packetClass && singletons.size() == 1) {
FieldAccessor accessor = Accessors.getFieldAccessor(singletons.get(0));
try {
accessor.get(null);
@@ -114,6 +132,23 @@ public class StructureCache {
} catch (Exception ignored) {
}
}
if (immutableClass) {
for (Field singleton : singletons) {
if (!Modifier.isFinal(singleton.getModifiers())) {
continue;
}
FieldAccessor accessor = Accessors.getFieldAccessor(singleton);
try {
Object instance = accessor.get(null);
if (instance != null) {
return () -> instance;
}
} catch (Exception ignored) {
}
}
}
}
try {
@@ -124,7 +159,10 @@ public class StructureCache {
} catch (Exception ignored) {
}
WrappedStreamCodec streamCodec = PacketRegistry.getStreamCodec(clazz);
// stream codecs only exist on 1.20.5+; on legacy versions asking the registry re-enters its own
// initialization (createOldRegister builds StructureModifiers, which land back here)
WrappedStreamCodec streamCodec = MinecraftVersion.v1_20_5.atOrAbove()
? PacketRegistry.getStreamCodec(clazz) : null;
// use the new stream codec for versions above 1.20.5 if possible
if (streamCodec != null && tryInitTrickDataSerializer()) {
@@ -13,6 +13,7 @@ import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.wrappers.Converters;
public class OutboundPacketListenerSet extends PacketListenerSet {
@@ -31,36 +32,42 @@ public class OutboundPacketListenerSet extends PacketListenerSet {
if (event.getPacketType() == PacketType.Play.Server.BUNDLE && !event.isCancelled()) {
// unpack the bundle and invoke for each packet in the bundle
Iterable<PacketContainer> packets = event.getPacket().getPacketBundles().read(0);
List<PacketContainer> outPackets = new ArrayList<>();
for (PacketContainer subPacket : packets) {
// ignore null packets as the will throw an error in the packet encoder
if (subPacket == null) {
continue;
List<PacketContainer> packets = Converters.toList(event.getPacket().getPacketBundles().read(0));
// lazily allocated when the first packet is removed or replaced
List<PacketContainer> outPackets = null;
for (int index = 0; index < packets.size(); index++) {
PacketContainer subPacket = packets.get(index);
PacketContainer packet = null;
if (subPacket != null) {
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(),
event.getPlayer(), event.isFiltered(), event);
super.invoke(subPacketEvent, priorityFilter);
if (!subPacketEvent.isCancelled()) {
PacketContainer result = subPacketEvent.getPacket();
if (result != null && result.getHandle() != null) {
packet = result;
}
}
}
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(),
event.getPlayer());
super.invoke(subPacketEvent, priorityFilter);
// if the packet has been cancelled, the packet will not be add to the bundle
if (subPacketEvent.isCancelled()) {
continue;
boolean changed = packet == null || packet.getHandle() != subPacket.getHandle();
if (outPackets == null && changed) {
outPackets = new ArrayList<>(packets.size());
outPackets.addAll(packets.subList(0, index));
}
PacketContainer packet = subPacketEvent.getPacket();
if (packet == null || packet.getHandle() == null) {
// super.invoke() should prevent us from getting new null packet so we just ignore it here
continue;
} else {
if (outPackets != null && packet != null) {
outPackets.add(packet);
}
}
if (!event.isReadOnly()) {
if (packets.iterator().hasNext()) {
event.getPacket().getPacketBundles().write(0, outPackets);
if (!event.isReadOnly() && outPackets != null) {
if (!outPackets.isEmpty()) {
// the original bundle may be shared between multiple recipients
PacketContainer bundle = event.getPacket().shallowClone();
bundle.getPacketBundles().write(0, outPackets);
event.setPacket(bundle);
} else {
// cancel entire packet if each individual packet has been cancelled
event.setCancelled(true);
@@ -1,6 +1,7 @@
package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
@@ -67,6 +68,24 @@ public interface Injector {
*/
Player getPlayer();
/**
* Retrieve the player name captured during login.
*
* @return The player name, or NULL if it is not known yet.
*/
default String getPlayerName() {
return null;
}
/**
* Retrieve the player UUID captured during login.
*
* @return The player UUID, or NULL if it is not available.
*/
default UUID getPlayerUniqueId() {
return null;
}
/**
* Set the current player instance.
*
@@ -19,10 +19,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
@@ -81,6 +84,11 @@ public class WirePacket {
public static byte[] bytesFromPacket(PacketContainer packet) {
checkNotNull(packet, "packet cannot be null!");
// packets are written by their stream codec since 1.20.5
if (MinecraftVersion.v1_20_5.atOrAbove()) {
return bytesFromStreamCodec(packet);
}
ByteBuf buffer = PacketContainer.createPacketBuffer();
ByteBuf store = PacketContainer.createPacketBuffer();
@@ -107,6 +115,44 @@ public class WirePacket {
return bytes;
}
private static byte[] bytesFromStreamCodec(PacketContainer packet) {
// Spigot keeps the data of unknown custom payloads in a buffer and the codec reads from it,
// so the reader index is restored to keep the packet writable after this call
ByteBuf payloadData = getCustomPayloadData(packet);
int readerIndex = payloadData != null ? payloadData.readerIndex() : 0;
try {
ByteBuf buffer = (ByteBuf) packet.serializeToBuffer();
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
} finally {
if (payloadData != null) {
payloadData.readerIndex(readerIndex);
}
}
}
private static ByteBuf getCustomPayloadData(PacketContainer packet) {
PacketType type = packet.getType();
if (type != PacketType.Play.Server.CUSTOM_PAYLOAD
&& type != PacketType.Play.Client.CUSTOM_PAYLOAD
&& type != PacketType.Configuration.Server.CUSTOM_PAYLOAD
&& type != PacketType.Configuration.Client.CUSTOM_PAYLOAD) {
return null;
}
Object payload = packet.getModifier()
.withType(CustomPacketPayloadWrapper.getCustomPacketPayloadClass())
.readSafely(0);
if (payload == null) {
return null;
}
StructureModifier<ByteBuf> buffers = new StructureModifier<>(payload.getClass())
.withTarget(payload)
.withType(ByteBuf.class);
return buffers.readSafely(0);
}
/**
* Creates a WirePacket from an existing Minecraft packet
*
@@ -118,6 +164,10 @@ public class WirePacket {
checkNotNull(packet, "packet cannot be null!");
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
if (MinecraftVersion.v1_20_5.atOrAbove()) {
return fromPacket(PacketContainer.fromPacket(packet));
}
ByteBuf buffer = PacketContainer.createPacketBuffer();
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
@@ -1,6 +1,7 @@
package com.comphenix.protocol.injector.netty.channel;
import java.net.SocketAddress;
import java.util.UUID;
import org.bukkit.entity.Player;
@@ -67,6 +68,16 @@ final class EmptyInjector implements Injector {
return this.player;
}
@Override
public String getPlayerName() {
return this.player != null ? this.player.getName() : null;
}
@Override
public UUID getPlayerUniqueId() {
return this.player != null ? this.player.getUniqueId() : null;
}
@Override
public void setPlayer(Player player) {
this.player = player;
@@ -4,7 +4,9 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
@@ -31,6 +33,7 @@ import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
@@ -100,6 +103,7 @@ public class NettyChannelInjector implements Injector {
// information about the player belonging to this injector
private String playerName;
private UUID playerUniqueId;
private Player player;
// lazy initialized fields, if we don't need them we don't bother about them
@@ -115,6 +119,10 @@ public class NettyChannelInjector implements Injector {
) {
// bukkit stuff
this.player = player;
if (player != null && !(player instanceof TemporaryPlayer)) {
this.playerName = player.getName();
this.playerUniqueId = player.getUniqueId();
}
// protocol lib stuff
this.errorReporter = errorReporter;
@@ -361,12 +369,23 @@ public class NettyChannelInjector implements Injector {
return this.player;
}
@Override
public String getPlayerName() {
return this.playerName;
}
@Override
public UUID getPlayerUniqueId() {
return this.playerUniqueId;
}
@Override
public void setPlayer(Player player) {
this.injectionFactory.invalidate(this.player, this.playerName);
this.player = player;
this.playerName = player.getName();
this.playerUniqueId = player.getUniqueId();
this.injectionFactory.cacheInjector(player, this);
this.injectionFactory.cacheInjector(player.getName(), this);
@@ -421,14 +440,19 @@ public class NettyChannelInjector implements Injector {
// process login packets for the player name
if (packet.getType() == PacketType.Login.Client.START) {
String username;
UUID uniqueId;
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
// 1.19 replaced the gameprofile with username and uuid field
username = packet.getStrings().readSafely(0);
uniqueId = readLoginUniqueId(packet);
} else {
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
username = profile != null ? profile.getName() : null;
uniqueId = profile != null ? profile.getUUID() : null;
}
this.playerUniqueId = uniqueId;
if (username != null) {
this.playerName = username;
this.injectionFactory.cacheInjector(username, this);
@@ -443,6 +467,22 @@ public class NettyChannelInjector implements Injector {
}
}
private static UUID readLoginUniqueId(PacketContainer packet) {
UUID uniqueId = packet.getUUIDs().readSafely(0);
if (uniqueId != null) {
return uniqueId;
}
StructureModifier<Optional> optionals = packet.getModifier().withType(Optional.class);
for (int i = 0; i < optionals.size(); i++) {
Optional<?> optional = optionals.readSafely(i);
if (optional != null && optional.orElse(null) instanceof UUID optionalUniqueId) {
return optionalUniqueId;
}
}
return null;
}
private void processInboundInternal(ChannelHandlerContext ctx, PacketContainer packetContainer) {
if (this.listenerManager.hasMainThreadListener(packetContainer.getType()) && !Bukkit.isPrimaryThread()) {
// not on the main thread but we are required to be reschedule the packet on the
@@ -516,15 +556,16 @@ public class NettyChannelInjector implements Injector {
return action;
}
PacketContainer packetContainer = new PacketContainer(packetType, packet);
// ensure that we are on the main thread if we need to
if (this.listenerManager.hasMainThreadListener(packetType) && !Bukkit.isPrimaryThread()) {
if (!Bukkit.isPrimaryThread() && hasMainThreadListener(this.listenerManager, protocol, packetContainer)) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, null, true));
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, marker, true));
return null;
}
// create event and invoke listeners
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player);
this.listenerManager.invokeOutboundPacketListeners(event);
@@ -554,6 +595,44 @@ public class NettyChannelInjector implements Injector {
return null;
}
/**
* Checks if a listener that must be called on the main thread is going to be invoked for the given outbound packet.
* The packets inside a bundle are posted to their listeners together with the bundle, so their listeners count for
* the bundle as well.
*
* @param listenerManager the listener manager holding the registered listeners.
* @param protocol the current outbound protocol of the connection.
* @param packet the outbound packet.
* @return true if the packet must be processed on the main thread, false otherwise.
*/
static boolean hasMainThreadListener(ListenerManager listenerManager, Protocol protocol, PacketContainer packet) {
if (listenerManager.hasMainThreadListener(packet.getType())) {
return true;
}
if (packet.getType() != PacketType.Play.Server.BUNDLE) {
return false;
}
Iterable<?> subPackets = packet.getModifier().<Iterable<?>>withType(Iterable.class).readSafely(0);
if (subPackets == null) {
return false;
}
for (Object subPacket : subPackets) {
if (subPacket == null) {
continue;
}
PacketType subPacketType = PacketRegistry.getPacketType(protocol, subPacket.getClass());
if (subPacketType != null && listenerManager.hasMainThreadListener(subPacketType)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
private <T> T proxyAction(T action, PacketEvent event, NetworkMarker marker) {
// hack - we only know that the given action is either a runnable or callable, but we need to work out which thing
@@ -15,6 +15,7 @@ import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.internal.PlatformProvider;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
@@ -23,7 +24,9 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
public class NetworkManagerInjector {
@@ -36,7 +39,9 @@ public class NetworkManagerInjector {
private final InjectionFactory injectionFactory;
// netty handler
private final InjectionChannelInboundHandler injectionHandler;
private final InjectionChannelInitializer pipelineInjectorHandler;
private Runnable paperChannelInitializerCleanup;
// status of this injector
private boolean closed = false;
@@ -47,22 +52,35 @@ public class NetworkManagerInjector {
this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager);
// hooking netty handlers
InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler(
this.injectionHandler = new InjectionChannelInboundHandler(
this.errorReporter,
this.injectionFactory);
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
this.pipelineInjectorHandler = new InjectionChannelInitializer(
INBOUND_INJECT_HANDLER_NAME,
this.injectionHandler);
}
public Injector getInjector(Player player) {
return this.injectionFactory.fromPlayer(player);
}
public boolean isUsingPaperChannelInitializer() {
return this.paperChannelInitializerCleanup != null;
}
@SuppressWarnings("unchecked")
public void inject() {
if (this.closed || this.injected) {
return;
}
PlatformProvider platformProvider = PlatformProvider.get();
if (platformProvider.hasEarlyChannelInitialization()) {
this.paperChannelInitializerCleanup = platformProvider.registerChannelInitializer(this::injectChannel);
this.injected = true;
return;
}
// get all "server connections" defined in the minecraft server class
FuzzyReflection server = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
List<Method> serverConnectionGetter = server.getMethodList(FuzzyMethodContract.newBuilder()
@@ -125,6 +143,23 @@ public class NetworkManagerInjector {
this.injected = true;
}
private void injectChannel(Channel channel) {
addInjectionHandler(channel, MinecraftReflection.getNetworkManagerClass(), this.injectionHandler);
}
static void addInjectionHandler(Channel channel, Class<?> networkManagerClass, ChannelHandler injectionHandler) {
for (String handlerName : channel.pipeline().names()) {
ChannelHandler handler = channel.pipeline().get(handlerName);
if (networkManagerClass.isInstance(handler)) {
channel.pipeline().addBefore(handlerName, INBOUND_INJECT_HANDLER_NAME, injectionHandler);
return;
}
}
// Preserve the existing behavior for forks with an unexpected pipeline layout.
channel.pipeline().addLast(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
}
public void close() {
if (this.closed || !this.injected) {
return;
@@ -153,6 +188,13 @@ public class NetworkManagerInjector {
// clear up
this.overriddenLists.clear();
this.injectionFactory.close();
try {
if (this.paperChannelInitializerCleanup != null) {
this.paperChannelInitializerCleanup.run();
}
} finally {
this.paperChannelInitializerCleanup = null;
this.injectionFactory.close();
}
}
}
@@ -19,6 +19,7 @@ package com.comphenix.protocol.injector.temporary;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -132,7 +133,16 @@ public class TemporaryPlayerFactory {
if (methodName.equals("isOnline")) {
return injector.isConnected();
} else if (methodName.equals("getName")) {
String playerName = injector.getPlayerName();
if (playerName != null) {
return playerName;
}
return "UNKNOWN[" + injector.getAddress() + "]";
} else if (methodName.equals("getUniqueId")) {
UUID playerUniqueId = injector.getPlayerUniqueId();
if (playerUniqueId != null) {
return playerUniqueId;
}
}
// Ignore all other methods
@@ -184,14 +194,16 @@ public class TemporaryPlayerFactory {
* <li>getPlayer()</li>
* <li>getAddress()</li>
* <li>getServer()</li>
* <li>getName()</li>
* <li>getUniqueId(), when supplied by the login protocol</li>
* <li>chat(String)</li>
* <li>sendMessage(String)</li>
* <li>sendMessage(String[])</li>
* <li>kickPlayer(String)</li>
* </ul>
* <p>
* Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead.
* Before the login start packet is received, the player has no profile information
* and can only be identified by its address.
*
* @return A temporary player instance.
*/
@@ -0,0 +1,35 @@
package com.comphenix.protocol.internal;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import org.bukkit.command.CommandExecutor;
import com.comphenix.protocol.ProtocolLib;
import io.netty.channel.Channel;
public interface PlatformProvider {
static PlatformProvider get() {
return Holder.INSTANCE;
}
boolean hasEarlyChannelInitialization();
Runnable registerChannelInitializer(Consumer<Channel> channelInitializer);
void registerCommand(ProtocolLib plugin, String name, CommandExecutor executor);
final class Holder {
private static final PlatformProvider INSTANCE = ServiceLoader.load(
PlatformProvider.class,
PlatformProvider.class.getClassLoader())
.findFirst()
.orElseThrow(() -> new IllegalStateException("No ProtocolLib platform provider is available"));
private Holder() {
}
}
}
@@ -90,6 +90,8 @@ public class StructureModifier<T> {
protected Map<FieldAccessor, Integer> defaultFields;
// Cache of previous types
protected Map<Class<?>, StructureModifier<?>> subtypeCache;
// Cache of previous parameterized types
private Map<List<Type>, StructureModifier<?>> paramTypeCache = new HashMap<>();
// Whether or subclasses should handle conversion
protected boolean customConvertHandling;
@@ -201,6 +203,7 @@ public class StructureModifier<T> {
other.defaultFields,
other.converter,
other.subtypeCache);
this.paramTypeCache = other.paramTypeCache;
}
/**
@@ -531,8 +534,18 @@ public class StructureModifier<T> {
return (StructureModifier<R>) NO_OP_MODIFIER;
}
// parameterized lookups must not share a cache entry with the raw field type
List<Type> paramTypeKey = null;
if (paramTypes.length > 0) {
paramTypeKey = new ArrayList<>(paramTypes.length + 1);
paramTypeKey.add(fieldType);
paramTypeKey.addAll(Arrays.asList(paramTypes));
}
// Do we need to update the cache?
StructureModifier<R> result = (StructureModifier<R>) this.subtypeCache.get(fieldType);
StructureModifier<R> result = (StructureModifier<R>) (paramTypeKey == null
? this.subtypeCache.get(fieldType)
: this.paramTypeCache.get(paramTypeKey));
if (result == null) {
List<FieldAccessor> fields = new ArrayList<>();
Map<FieldAccessor, Integer> defaults = new HashMap<>();
@@ -571,7 +584,11 @@ public class StructureModifier<T> {
// Cache structure modifiers
result = this.withFieldType(fieldType, fields, defaults);
this.subtypeCache.put(fieldType, result);
if (paramTypeKey == null) {
this.subtypeCache.put(fieldType, result);
} else {
this.paramTypeCache.put(paramTypeKey, result);
}
}
// Add the target too
@@ -63,6 +63,7 @@ public class ImmutableDetector implements Cloner {
add(MinecraftReflection::getBlockClass);
add(MinecraftReflection::getItemClass);
add(MinecraftReflection::getSoundEffectClass);
add(MinecraftReflection::getWorldTypeClass);
if (MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
add(MinecraftReflection::getFluidTypeClass);
@@ -0,0 +1,58 @@
/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2015 dmulloy2
* <p>
* 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 2 of
* the License, or (at your option) any later version.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.updater;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
/**
* Update check worker (top-level class so the runtime always loads a single {@code .class} entry).
*/
final class SpigotUpdateRunnable implements Runnable {
private final SpigotUpdater updater;
SpigotUpdateRunnable(SpigotUpdater updater) {
this.updater = updater;
}
@Override
public void run() {
try {
String version = this.updater.getSpigotVersion();
this.updater.setRemoteVersion(version);
if (this.updater.versionCheck(version)) {
this.updater.result = Updater.UpdateResult.SPIGOT_UPDATE_AVAILABLE;
} else {
this.updater.result = Updater.UpdateResult.NO_UPDATE;
}
} catch (Throwable ex) {
if (ProtocolLibrary.getConfig().isDebug()) {
ProtocolLibrary.getErrorReporter().reportDetailed(
this.updater,
Report.newBuilder(Updater.REPORT_CANNOT_UPDATE_PLUGIN).error(ex).callerParam(this));
}
ProtocolLibrary.disableUpdates();
} finally {
for (Runnable listener : this.updater.listeners) {
ProtocolLibrary.getScheduler().runTask(listener);
}
}
}
}
@@ -16,8 +16,6 @@
*/
package com.comphenix.protocol.updater;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.utility.Closer;
import org.bukkit.plugin.Plugin;
@@ -44,7 +42,7 @@ public final class SpigotUpdater extends Updater {
waitForThread();
this.type = type;
this.thread = new Thread(new SpigotUpdateRunnable());
this.thread = new Thread(new SpigotUpdateRunnable(this));
this.thread.start();
}
@@ -54,36 +52,8 @@ public final class SpigotUpdater extends Updater {
return String.format(result.toString(), remoteVersion, plugin.getDescription().getVersion(), RESOURCE_URL);
}
private class SpigotUpdateRunnable implements Runnable {
@Override
public void run() {
try {
String version = getSpigotVersion();
remoteVersion = version;
if (versionCheck(version)) {
result = UpdateResult.SPIGOT_UPDATE_AVAILABLE;
} else {
result = UpdateResult.NO_UPDATE;
}
} catch (Throwable ex) {
if (ProtocolLibrary.getConfig().isDebug()) {
ProtocolLibrary.getErrorReporter().reportDetailed(
SpigotUpdater.this, Report.newBuilder(REPORT_CANNOT_UPDATE_PLUGIN).error(ex).callerParam(this));
} else {
// People don't care
// plugin.getLogger().log(Level.WARNING, "Failed to check for updates: " + ex);
}
ProtocolLibrary.disableUpdates();
} finally {
// Invoke the listeners on the main thread
for (Runnable listener : listeners) {
ProtocolLibrary.getScheduler().runTask(listener);
}
}
}
void setRemoteVersion(String remoteVersion) {
this.remoteVersion = remoteVersion;
}
private static final String RESOURCE_URL = "https://www.spigotmc.org/resources/protocollib.1997/";
@@ -57,21 +57,25 @@ public final class MinecraftMethods {
}
public static Function<ByteBuf, Object> getFriendlyBufBufConstructor() {
if (friendlyBufBufConstructor == null) {
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(registryByteBuf.get()).getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
.build()));
friendlyBufBufConstructor = (byteBuf) -> accessor.invoke(byteBuf, MinecraftRegistryAccess.get());
} else {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(MinecraftReflection.getPacketDataSerializerClass(), ByteBuf.class);
friendlyBufBufConstructor = (byteBuf) -> accessor.invoke(byteBuf);
}
}
return friendlyBufBufConstructor;
if (friendlyBufBufConstructor == null) {
Optional<Class<?>> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass();
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(
FuzzyReflection.fromClass(registryByteBuf.get()).getConstructor(
FuzzyMethodContract.newBuilder()
.parameterExactType(ByteBuf.class, 0)
.parameterExactType(MinecraftReflection.getRegistryAccessClass(), 1)
.build()));
friendlyBufBufConstructor = byteBuf -> accessor.invoke(byteBuf, MinecraftRegistryAccess.get());
} else {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(
MinecraftReflection.getPacketDataSerializerClass(),
ByteBuf.class);
friendlyBufBufConstructor = byteBuf -> accessor.invoke(byteBuf);
}
}
return friendlyBufBufConstructor;
}
/**
@@ -101,6 +101,7 @@ public final class MinecraftProtocolVersion {
map.put(new MinecraftVersion(26, 1, 0), 775);
map.put(new MinecraftVersion(26, 2, 0), 776);
map.put(new MinecraftVersion(26, 3, 0), 777);
return map;
}
@@ -1430,8 +1430,13 @@ public final class MinecraftReflection {
try {
Method asMirrorMethod = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass());
asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex);
} catch (ReflectiveOperationException ex1) {
try {
Method asMirrorMethod = getCraftItemStackClass().getMethod("asBukkitMirror", getItemStackClass());
asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod);
} catch (ReflectiveOperationException ex2) {
throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex2);
}
}
}
@@ -1937,4 +1942,22 @@ public final class MinecraftReflection {
public static Class<?> getPositionMoveRotationClass() {
return getMinecraftClass("world.entity.PositionMoveRotation");
}
/**
* Retrieves the VecDelta class (since 26.3).
*
* @return The VecDelta class.
*/
public static Optional<Class<?>> getVecDeltaClass() {
return getOptionalNMS("network.protocol.game.VecDelta");
}
/**
* Retrieves the PositionPath class (since 26.3).
*
* @return The PositionPath class.
*/
public static Optional<Class<?>> getPositionPathClass() {
return getOptionalNMS("world.entity.PositionPath");
}
}
@@ -36,6 +36,11 @@ import org.bukkit.Server;
* @author Kristian
*/
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
/**
* Version 26.3 - wilderness bound
*/
public static final MinecraftVersion v26_3 = new MinecraftVersion("26.3");
/**
* Version 26.2 - chaos cubed
*/
@@ -194,7 +199,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
/**
* The latest release version of minecraft.
*/
public static final MinecraftVersion LATEST = v26_2;
public static final MinecraftVersion LATEST = v26_3;
// used when serializing
private static final long serialVersionUID = -8695133558996459770L;
@@ -326,15 +326,9 @@ public class StreamSerializer {
public byte[] getBytesAndRelease(ByteBuf buf) {
try {
if (buf.hasArray()) {
// heap buffer, we can access the array directly
return buf.array();
} else {
// direct buffer, we need to copy the bytes into an array
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
return bytes;
}
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
return bytes;
} finally {
ReferenceCountUtil.safeRelease(buf);
}
@@ -1,3 +0,0 @@
package com.comphenix.protocol.utility;
public record Tuple<A, B>(A first, B second) {}
@@ -22,7 +22,6 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.google.common.base.Defaults;
import com.google.common.base.Preconditions;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
@@ -166,67 +165,16 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
nmsInstanceCreator = noArgs;
nmsDefaultArgs = NO_ARGS;
} else {
// No no-arg constructor. For records (and most NMS data classes) the canonical
// constructor has one parameter per non-static field; that's the one we want.
// Other constructors (e.g. FriendlyByteBuf decoders) take a single network-buffer
// argument and would call methods on it during construction, so they're unsafe to
// invoke with default values.
Constructor<?>[] ctors = nmsClass.getDeclaredConstructors();
Constructor<?> chosen = selectConstructor(ctors);
nmsInstanceCreator = Accessors.getConstructorAccessor(chosen);
Class<?>[] paramTypes = chosen.getParameterTypes();
nmsDefaultArgs = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
nmsDefaultArgs[i] = paramTypes[i].isPrimitive()
? Defaults.defaultValue(paramTypes[i])
: null;
}
// use the first constructor of the class
nmsInstanceCreator = Accessors.getConstructorAccessor(nmsClass.getDeclaredConstructors()[0]);
nmsDefaultArgs = Arrays
.stream(nmsInstanceCreator.getConstructor().getParameterTypes())
.map(type -> type.isPrimitive() ? Defaults.defaultValue(type) : null)
.toArray(Object[]::new);
}
}
}
private Constructor<?> selectConstructor(Constructor<?>[] ctors) {
Constructor<?> fallback = null;
for (Constructor<?> ctor : ctors) {
if (isNetworkBufferConstructor(ctor)) {
continue;
}
if (ctor.getParameterCount() == nmsAccessors.length) {
return ctor;
}
if (fallback == null || ctor.getParameterCount() > fallback.getParameterCount()) {
fallback = ctor;
}
}
if (fallback != null) {
return fallback;
}
throw new InvalidWrapperException(nmsClass.getSimpleName()
+ " has no safe constructor for default instantiation", null);
}
private boolean isNetworkBufferConstructor(Constructor<?> ctor) {
Class<?> packetDataSerializer = MinecraftReflection.getPacketDataSerializerClass();
Class<?> registryByteBuf = MinecraftReflection.getRegistryFriendlyByteBufClass().orElse(null);
for (Class<?> param : ctor.getParameterTypes()) {
// Only treat the parameter as a network buffer if it's the buffer type itself or a subtype.
// Using the reverse direction (param.isAssignableFrom(...)) would also exclude legitimate
// ctors taking ByteBuf / Object / etc. as record components.
if (packetDataSerializer.isAssignableFrom(param)) {
return true;
}
if (registryByteBuf != null && registryByteBuf.isAssignableFrom(param)) {
return true;
}
}
return false;
}
// ---- Equivalent conversion
@Override
@@ -1440,6 +1440,10 @@ public class BukkitConverters {
// and avoids re-running fuzzy lookups in concurrent injection paths.
private static volatile MethodAccessor damageTypeToNms = null;
private static volatile MethodAccessor damageTypeFromNms = null;
private static volatile MethodAccessor damageTypeHolderValue = null;
private static boolean damageTypeToNmsReturnsHolder;
private static boolean damageTypeFromNmsAcceptsHolder;
private static final Map<Object, Object> damageTypeHolders = new ConcurrentHashMap<>();
private static FuzzyReflection craftDamageTypeFuzzy() {
return FuzzyReflection.fromClass(getCraftBukkitClass("damage.CraftDamageType"), false);
@@ -1451,11 +1455,20 @@ public class BukkitConverters {
synchronized (BukkitConverters.class) {
accessor = damageTypeToNms;
if (accessor != null) return accessor;
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(org.bukkit.damage.DamageType.class)
.returnTypeExact(MinecraftReflection.getDamageTypeClass())
.requireModifier(Modifier.STATIC)
.build()));
try {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(org.bukkit.damage.DamageType.class)
.returnTypeExact(MinecraftReflection.getDamageTypeClass())
.requireModifier(Modifier.STATIC)
.build()));
} catch (IllegalArgumentException exception) {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(org.bukkit.damage.DamageType.class)
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.STATIC)
.build()));
damageTypeToNmsReturnsHolder = true;
}
damageTypeToNms = accessor;
return accessor;
}
@@ -1467,16 +1480,68 @@ public class BukkitConverters {
synchronized (BukkitConverters.class) {
accessor = damageTypeFromNms;
if (accessor != null) return accessor;
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getDamageTypeClass())
.returnTypeExact(org.bukkit.damage.DamageType.class)
.requireModifier(Modifier.STATIC)
.build()));
try {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getDamageTypeClass())
.returnTypeExact(org.bukkit.damage.DamageType.class)
.requireModifier(Modifier.STATIC)
.build()));
} catch (IllegalArgumentException exception) {
accessor = Accessors.getMethodAccessor(craftDamageTypeFuzzy().getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getHolderClass())
.returnTypeExact(org.bukkit.damage.DamageType.class)
.requireModifier(Modifier.STATIC)
.build()));
damageTypeFromNmsAcceptsHolder = true;
}
damageTypeFromNms = accessor;
return accessor;
}
}
private static Object getHolderValue(Object holder) {
MethodAccessor accessor = damageTypeHolderValue;
if (accessor == null) {
synchronized (BukkitConverters.class) {
accessor = damageTypeHolderValue;
if (accessor == null) {
accessor = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(MinecraftReflection.getHolderClass(), false)
.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.banModifier(Modifier.STATIC)
.returnTypeExact(Object.class)
.build()));
damageTypeHolderValue = accessor;
}
}
}
return accessor.invoke(holder);
}
private static Object getDamageTypeHolder(Object generic) {
Object holder = damageTypeHolders.get(generic);
if (holder != null) {
return holder;
}
MethodAccessor accessor = resolveDamageTypeToNms();
if (!damageTypeToNmsReturnsHolder) {
throw new IllegalStateException("CraftDamageType accepts holders but does not produce them");
}
for (org.bukkit.damage.DamageType damageType : org.bukkit.Registry.DAMAGE_TYPE) {
Object candidate = accessor.invoke(null, damageType);
Object candidateValue = getHolderValue(candidate);
damageTypeHolders.putIfAbsent(candidateValue, candidate);
if (Objects.equal(candidateValue, generic)) {
return candidate;
}
}
throw new IllegalArgumentException("Unable to resolve a holder for damage type " + generic);
}
public static EquivalentConverter<org.bukkit.damage.DamageType> getDamageTypeConverter() {
return ignoreNull(new EquivalentConverter<org.bukkit.damage.DamageType>() {
@@ -1487,12 +1552,22 @@ public class BukkitConverters {
@Override
public Object getGeneric(org.bukkit.damage.DamageType specific) {
return resolveDamageTypeToNms().invoke(null, specific);
Object generic = resolveDamageTypeToNms().invoke(null, specific);
if (damageTypeToNmsReturnsHolder) {
Object value = getHolderValue(generic);
damageTypeHolders.putIfAbsent(value, generic);
return value;
}
return generic;
}
@Override
public org.bukkit.damage.DamageType getSpecific(Object generic) {
return (org.bukkit.damage.DamageType) resolveDamageTypeFromNms().invoke(null, generic);
MethodAccessor accessor = resolveDamageTypeFromNms();
if (damageTypeFromNmsAcceptsHolder) {
generic = getDamageTypeHolder(generic);
}
return (org.bukkit.damage.DamageType) accessor.invoke(null, generic);
}
});
}
@@ -18,6 +18,8 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -33,8 +35,8 @@ import net.bytebuddy.matcher.ElementMatchers;
* by default. Constructing a new wrapper instance will give out a handle to a completely new implemented type, that
* allows to set a key and some kind of data of any choice.
* <p>
* Note that constructing this class from a generic handle is only possible for the spigot-specific UnknownPayload type.
* All other payloads should be accessed via a structure modifier directly.
* Constructing this class from a generic handle requires either retained raw byte data, such as {@code DiscardedPayload},
* or a writable serialization method on the concrete payload type.
*
* @author Pasqual Koschmieder
*/
@@ -46,12 +48,16 @@ public final class CustomPacketPayloadWrapper {
private static final ConstructorAccessor PAYLOAD_WRAPPER_CONSTRUCTOR;
private static final MethodAccessor GET_ID_PAYLOAD_METHOD;
private static final MethodAccessor GET_TYPE_PAYLOAD_METHOD;
private static final MethodAccessor GET_ID_PAYLOAD_TYPE_METHOD;
private static final MethodAccessor GET_ID_HOLDER_PAYLOAD_METHOD;
private static final MethodAccessor GET_ID_PAYLOAD_HOLDER_METHOD;
private static final MethodAccessor SERIALIZE_PAYLOAD_METHOD;
private static final Class<?> PAYLOAD_SERIALIZER_CLASS;
private static final ConcurrentHashMap<Class<?>, Optional<MethodAccessor>> SERIALIZE_PAYLOAD_METHODS =
new ConcurrentHashMap<>();
/** True when running on MC 1.21.5+ where DiscardedPayload is used instead of the ByteBuddy proxy. */
/** True when running on the nested-ID API where DiscardedPayload is used instead of the ByteBuddy proxy. */
private static final boolean USE_DISCARDED_PAYLOAD;
private static final boolean DISCARDED_PAYLOAD_USES_BYTE_ARRAY;
private static final EquivalentConverter<CustomPacketPayloadWrapper> CONVERTER;
@@ -60,11 +66,9 @@ public final class CustomPacketPayloadWrapper {
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
// MC 1.21.5+: CustomPacketPayload no longer has id(); DiscardedPayload is the fallback payload
// MC < 1.21.5: CustomPacketPayload.id() returns Identifier directly
Method getPayloadId = null;
Method getPayloadType = null;
Method getPayloadTypeId = null;
Method getPayloadIdHolder = null;
Method getPayloadIdFromHolder = null;
boolean useDiscardedPayload = false;
try {
getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
@@ -73,17 +77,19 @@ public final class CustomPacketPayloadWrapper {
.parameterCount(0)
.build());
} catch (IllegalArgumentException ignored) {
// New API: CustomPacketPayload.type().id() exposes the identifier for every payload type.
getPayloadType = CUSTOM_PACKET_PAYLOAD_CLASS.getMethod("type");
getPayloadTypeId = getPayloadType.getReturnType().getMethod("id");
// Paper 26: the payload exposes its identifier through one intermediate record.
Method[] accessors = findChainedIdAccessors(CUSTOM_PACKET_PAYLOAD_CLASS, MINECRAFT_KEY_CLASS);
getPayloadIdHolder = accessors[0];
getPayloadIdFromHolder = accessors[1];
useDiscardedPayload = true;
}
GET_ID_PAYLOAD_METHOD = getPayloadId != null ? Accessors.getMethodAccessor(getPayloadId) : null;
GET_TYPE_PAYLOAD_METHOD = getPayloadType != null ? Accessors.getMethodAccessor(getPayloadType) : null;
GET_ID_PAYLOAD_TYPE_METHOD = getPayloadTypeId != null ? Accessors.getMethodAccessor(getPayloadTypeId) : null;
GET_ID_HOLDER_PAYLOAD_METHOD = getPayloadIdHolder != null
? Accessors.getMethodAccessor(getPayloadIdHolder) : null;
GET_ID_PAYLOAD_HOLDER_METHOD = getPayloadIdFromHolder != null
? Accessors.getMethodAccessor(getPayloadIdFromHolder) : null;
USE_DISCARDED_PAYLOAD = useDiscardedPayload;
// serialize method may not exist in new API
Method serializePayloadData = null;
try {
serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
@@ -93,21 +99,40 @@ public final class CustomPacketPayloadWrapper {
.parameterDerivedOf(ByteBuf.class, 0)
.build());
} catch (IllegalArgumentException ignored) {
// Not present in new API
// Paper 26 declares the write method on each concrete payload instead.
}
SERIALIZE_PAYLOAD_METHOD = serializePayloadData != null
? Accessors.getMethodAccessor(serializePayloadData) : null;
if (serializePayloadData == null) {
ByteBuf buffer = Unpooled.buffer();
try {
PAYLOAD_SERIALIZER_CLASS = MinecraftReflection.getPacketDataSerializer(buffer).getClass();
} finally {
if (buffer.refCnt() > 0) {
buffer.release();
}
}
} else {
PAYLOAD_SERIALIZER_CLASS = null;
}
Constructor<?> payloadWrapperConstructor;
boolean discardedPayloadUsesByteArray = false;
if (useDiscardedPayload) {
// New API: use DiscardedPayload(Identifier, ByteBuf) as the payload handle
Class<?> discardedPayloadClass = MinecraftReflection.getMinecraftClass(
"network.protocol.common.custom.DiscardedPayload");
payloadWrapperConstructor = discardedPayloadClass.getConstructor(
MINECRAFT_KEY_CLASS, ByteBuf.class);
try {
payloadWrapperConstructor = discardedPayloadClass.getConstructor(
MINECRAFT_KEY_CLASS, byte[].class);
discardedPayloadUsesByteArray = true;
} catch (NoSuchMethodException exception) {
payloadWrapperConstructor = discardedPayloadClass.getConstructor(
MINECRAFT_KEY_CLASS, ByteBuf.class);
}
} else {
payloadWrapperConstructor = makePayloadWrapper();
}
DISCARDED_PAYLOAD_USES_BYTE_ARRAY = discardedPayloadUsesByteArray;
PAYLOAD_WRAPPER_CONSTRUCTOR = Accessors.getConstructorAccessor(payloadWrapperConstructor);
CONVERTER = new EquivalentConverter<CustomPacketPayloadWrapper>() {
@@ -131,6 +156,41 @@ public final class CustomPacketPayloadWrapper {
}
}
private static Method[] findChainedIdAccessors(Class<?> payloadClass, Class<?> keyClass) {
Method payloadAccessor = null;
Method keyAccessor = null;
FuzzyMethodContract keyAccessorContract = FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(keyClass)
.parameterCount(0)
.build();
for (Method candidate : payloadClass.getMethods()) {
if (Modifier.isStatic(candidate.getModifiers()) || candidate.getParameterCount() != 0) {
continue;
}
Method candidateKeyAccessor;
try {
candidateKeyAccessor = FuzzyReflection.fromClass(candidate.getReturnType(), true)
.getMethod(keyAccessorContract);
} catch (IllegalArgumentException ignored) {
continue;
}
if (payloadAccessor != null) {
throw new IllegalArgumentException("Multiple CustomPacketPayload identifier accessors found");
}
payloadAccessor = candidate;
keyAccessor = candidateKeyAccessor;
}
if (payloadAccessor == null) {
throw new IllegalArgumentException("Unable to find a CustomPacketPayload identifier accessor");
}
return new Method[]{payloadAccessor, keyAccessor};
}
private static Constructor<?> makePayloadWrapper() throws Exception {
return new ByteBuddy()
.subclass(Object.class)
@@ -153,6 +213,64 @@ public final class CustomPacketPayloadWrapper {
.getConstructor(MinecraftReflection.getMinecraftKeyClass(), byte[].class);
}
private static Optional<MethodAccessor> findSerializePayloadMethod(Class<?> payloadClass) {
Method serializeMethod = null;
for (Method candidate : FuzzyReflection.fromClass(payloadClass, true).getMethods()) {
if (Modifier.isStatic(candidate.getModifiers())
|| candidate.getReturnType() != void.class
|| candidate.getParameterCount() != 1) {
continue;
}
Class<?> parameterClass = candidate.getParameterTypes()[0];
if (parameterClass == Object.class || !parameterClass.isAssignableFrom(PAYLOAD_SERIALIZER_CLASS)) {
continue;
}
if (serializeMethod != null) {
throw new IllegalArgumentException("Multiple CustomPacketPayload serialization methods found on "
+ payloadClass.getName());
}
serializeMethod = candidate;
}
if (serializeMethod == null) {
return Optional.empty();
}
serializeMethod.setAccessible(true);
return Optional.of(Accessors.getMethodAccessor(serializeMethod));
}
private static MethodAccessor getSerializePayloadMethod(Object payload) {
if (SERIALIZE_PAYLOAD_METHOD != null) {
return SERIALIZE_PAYLOAD_METHOD;
}
return SERIALIZE_PAYLOAD_METHODS
.computeIfAbsent(payload.getClass(), CustomPacketPayloadWrapper::findSerializePayloadMethod)
.orElse(null);
}
private static byte[] serializePayload(Object payload) {
MethodAccessor serializeMethod = getSerializePayloadMethod(payload);
if (serializeMethod == null) {
throw new UnsupportedOperationException(
"Cannot extract raw custom payload bytes from " + payload.getClass().getName()
+ ": payload has no byte array, ByteBuf, or serialization method");
}
ByteBuf buffer = Unpooled.buffer();
try {
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
serializeMethod.invoke(payload, serializer);
byte[] result = new byte[buffer.readableBytes()];
buffer.getBytes(buffer.readerIndex(), result);
return result;
} finally {
if (buffer.refCnt() > 0) {
buffer.release();
}
}
}
// ====== api methods ======
/**
@@ -213,25 +331,19 @@ public final class CustomPacketPayloadWrapper {
// we read and retain the underlying buffer in case the class uses a buffer to store the data
// this way, when passing the packet to further handling, the buffer is not released and can be re-used
StructureModifier<Object> modifier = new StructureModifier<>(payload.getClass()).withTarget(payload);
byte[] messagePayload = modifier.withType(ByteBuf.class).optionRead(0)
.map(buffer -> {
ByteBuf buf = (ByteBuf) buffer;
byte[] data = StreamSerializer.getDefault().getBytesAndRelease(buf.markReaderIndex().retain());
buf.resetReaderIndex();
return data;
})
.orElseGet(() -> {
if (SERIALIZE_PAYLOAD_METHOD == null) {
throw new UnsupportedOperationException(
"Cannot extract raw custom payload bytes from " + payload.getClass().getName()
+ ": payload has no ByteBuf field and this Minecraft version does not "
+ "expose CustomPacketPayload serialization");
}
ByteBuf buffer = Unpooled.buffer();
Object serializer = MinecraftReflection.getPacketDataSerializer(buffer);
SERIALIZE_PAYLOAD_METHOD.invoke(payload, serializer);
return StreamSerializer.getDefault().getBytesAndRelease(buffer);
});
byte[] messagePayload = (byte[]) modifier.withType(byte[].class).readSafely(0);
if (messagePayload != null) {
messagePayload = messagePayload.clone();
} else {
messagePayload = modifier.withType(ByteBuf.class).optionRead(0)
.map(buffer -> {
ByteBuf buf = (ByteBuf) buffer;
byte[] data = StreamSerializer.getDefault().getBytesAndRelease(buf.markReaderIndex().retain());
buf.resetReaderIndex();
return data;
})
.orElseGet(() -> serializePayload(payload));
}
return new CustomPacketPayloadWrapper(messagePayload, id);
}
@@ -241,8 +353,8 @@ public final class CustomPacketPayloadWrapper {
if (GET_ID_PAYLOAD_METHOD != null) {
messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
} else {
Object payloadType = GET_TYPE_PAYLOAD_METHOD.invoke(payload);
messageId = GET_ID_PAYLOAD_TYPE_METHOD.invoke(payloadType);
Object idHolder = GET_ID_HOLDER_PAYLOAD_METHOD.invoke(payload);
messageId = GET_ID_PAYLOAD_HOLDER_METHOD.invoke(idHolder);
}
return MinecraftKey.getConverter().getSpecific(messageId);
}
@@ -284,7 +396,9 @@ public final class CustomPacketPayloadWrapper {
*/
public Object newHandle() {
if (USE_DISCARDED_PAYLOAD) {
// New API: DiscardedPayload(Identifier, ByteBuf)
if (DISCARDED_PAYLOAD_USES_BYTE_ARRAY) {
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), this.payload);
}
ByteBuf buf = Unpooled.copiedBuffer(this.payload);
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), buf);
}
@@ -187,7 +187,8 @@ public abstract class EnumWrappers {
DROP_ITEM,
RELEASE_USE_ITEM,
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND"),
STAB;
STAB,
CHANGE_DESTROY_DIRECTION;
final String[] aliases;
PlayerDigType(String... aliases) {
@@ -724,11 +725,6 @@ public abstract class EnumWrappers {
MOTION_BLOCKING_NO_LEAVES,
}
public enum RelativeArgument {
// Order must match the NMS Relative enum exactly.
X, Y, Z, Y_ROT, X_ROT, DELTA_X, DELTA_Y, DELTA_Z, ROTATE_DELTA
}
private static Class<?> PROTOCOL_CLASS = null;
private static Class<?> CLIENT_COMMAND_CLASS = null;
private static Class<?> CHAT_VISIBILITY_CLASS = null;
@@ -757,7 +753,6 @@ public abstract class EnumWrappers {
private static Class<?> TEAM_COLLISION_RULE_CLASS = null;
private static Class<?> TEAM_VISIBILITY_CLASS = null;
private static Class<?> HEIGHTMAP_TYPE_CLASS = null;
private static Class<?> RELATIVE_CLASS = null;
private static boolean INITIALIZING = false;
private static boolean INITIALIZED = false;
@@ -809,7 +804,10 @@ public abstract class EnumWrappers {
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
// the particle enum was removed in 1.13, later packets may contain unrelated enums
if (!MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) {
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
}
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
@@ -880,8 +878,6 @@ public abstract class EnumWrappers {
"world.level.levelgen.HeightMap$Type" /* Spigot Mapping */,
"world.level.levelgen.Heightmap$Types" /* Mojang Mapping */);
RELATIVE_CLASS = MinecraftReflection.getMinecraftClass("world.entity.Relative");
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
@@ -909,7 +905,6 @@ public abstract class EnumWrappers {
associate(TEAM_COLLISION_RULE_CLASS, TeamCollisionRule.class, getTeamCollisionRuleConverter());
associate(TEAM_VISIBILITY_CLASS, TeamVisibility.class, getTeamVisibilityConverter());
associate(HEIGHTMAP_TYPE_CLASS, HeightmapType.class, getHeightmapTypeConverter());
associate(RELATIVE_CLASS, RelativeArgument.class, getRelativeArgumentConverter());
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
@@ -1099,11 +1094,6 @@ public abstract class EnumWrappers {
return HEIGHTMAP_TYPE_CLASS;
}
public static Class<?> getRelativeArgumentClass() {
initialize();
return RELATIVE_CLASS;
}
// Get the converters
public static EquivalentConverter<Protocol> getProtocolConverter() {
return new EnumConverter<>(getProtocolClass(), Protocol.class);
@@ -1213,10 +1203,6 @@ public abstract class EnumWrappers {
return new EnumConverter<>(getHeightmapTypeClass(), HeightmapType.class);
}
public static EquivalentConverter<RelativeArgument> getRelativeArgumentConverter() {
return new EnumConverter<>(getRelativeArgumentClass(), RelativeArgument.class);
}
/**
* @since 1.13+
* @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found)
@@ -105,17 +105,22 @@ public class LegacyDataWatcher extends AbstractWrapper implements IDataWatcher {
*/
@Deprecated
public LegacyDataWatcher(List<WrappedWatchableObject> objects) {
this(newHandle(fakeEntity(), objects));
this(newHandle(fakeEntity()));
for (WrappedWatchableObject object : objects) {
WrappedDataWatcherObject watcherObject = MinecraftReflection.watcherObjectExists()
? object.getWatcherObject()
: WrappedDataWatcherObject.fromIndex(object.getIndex());
setObject(watcherObject, object, false);
}
}
private static Object newHandle(Object entity, List<WrappedWatchableObject> objects) {
private static Object newHandle(Object entity) {
if (constructor == null) {
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass(),
MinecraftReflection.getArrayClass(MinecraftReflection.getDataWatcherItemClass()));
constructor = Accessors.getConstructorAccessor(HANDLE_TYPE, MinecraftReflection.getEntityClass());
}
Object[] genericItems = new Object[0];// (Object[]) ITEMS_CONVERTER.getGeneric(objects);
return constructor.invoke(entity, genericItems);
return constructor.invoke(entity);
}
private static Object fakeEntity() {
@@ -1,119 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.Objects;
import java.util.Optional;
/**
* Wrapper for {@code ChatType.Bound}.
*/
public final class WrappedChatTypeBound {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getMinecraftClass("network.chat.ChatType$Bound");
private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(
HANDLE_TYPE,
MinecraftReflection.getHolderClass(),
MinecraftReflection.getIChatBaseComponentClass(),
Optional.class);
private static final MethodAccessor GET_CHAT_TYPE = Accessors.getMethodAccessor(HANDLE_TYPE, "chatType");
private static final MethodAccessor GET_NAME = Accessors.getMethodAccessor(HANDLE_TYPE, "name");
private static final MethodAccessor GET_TARGET_NAME = Accessors.getMethodAccessor(HANDLE_TYPE, "targetName");
private static final EquivalentConverter<MinecraftKey> CHAT_TYPE_HOLDER_CONVERTER =
new EquivalentConverter<>() {
@Override
public Object getGeneric(MinecraftKey specific) {
return chatTypeHolderConverter().getGeneric(specific);
}
@Override
public MinecraftKey getSpecific(Object generic) {
return chatTypeHolderConverter().getSpecific(generic);
}
@Override
public Class<MinecraftKey> getSpecificType() {
return MinecraftKey.class;
}
};
public static final EquivalentConverter<WrappedChatTypeBound> CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedChatTypeBound specific) {
return CONSTRUCTOR.invoke(
CHAT_TYPE_HOLDER_CONVERTER.getGeneric(specific.chatType),
BukkitConverters.getWrappedChatComponentConverter().getGeneric(specific.name),
Converters.optional(BukkitConverters.getWrappedChatComponentConverter())
.getGeneric(specific.targetName));
}
@Override
public WrappedChatTypeBound getSpecific(Object generic) {
return new WrappedChatTypeBound(
CHAT_TYPE_HOLDER_CONVERTER.getSpecific(GET_CHAT_TYPE.invoke(generic)),
BukkitConverters.getWrappedChatComponentConverter().getSpecific(GET_NAME.invoke(generic)),
Converters.optional(BukkitConverters.getWrappedChatComponentConverter())
.getSpecific(GET_TARGET_NAME.invoke(generic)));
}
@Override
public Class<WrappedChatTypeBound> getSpecificType() {
return WrappedChatTypeBound.class;
}
});
private final MinecraftKey chatType;
private final WrappedChatComponent name;
private final Optional<WrappedChatComponent> targetName;
public WrappedChatTypeBound(MinecraftKey chatType, WrappedChatComponent name,
Optional<WrappedChatComponent> targetName) {
this.chatType = chatType;
this.name = name;
this.targetName = targetName;
}
public MinecraftKey getChatType() {
return chatType;
}
public WrappedChatComponent getName() {
return name;
}
public Optional<WrappedChatComponent> getTargetName() {
return targetName;
}
private static EquivalentConverter<MinecraftKey> chatTypeHolderConverter() {
WrappedRegistry registry = WrappedRegistry.getRegistryByNmsKey("core.registries.Registries", "CHAT_TYPE");
if (registry == null) {
throw new IllegalStateException("ChatType registry is not available");
}
return Converters.holder(registry.valueConverter(), registry);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedChatTypeBound that)) {
return false;
}
return Objects.equals(chatType, that.chatType)
&& Objects.equals(name, that.name)
&& Objects.equals(targetName, that.targetName);
}
@Override
public int hashCode() {
return Objects.hash(chatType, name, targetName);
}
}
@@ -1,86 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.Arrays;
import java.util.Objects;
/**
* Wrapper for {@code ClientboundChunksBiomesPacket.ChunkBiomeData}.
*/
public final class WrappedChunkBiomeData {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getMinecraftClass(
"network.protocol.game.ClientboundChunksBiomesPacket$ChunkBiomeData");
private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(
HANDLE_TYPE,
MinecraftReflection.getChunkCoordIntPair(),
byte[].class);
private static final MethodAccessor GET_POS = Accessors.getMethodAccessor(HANDLE_TYPE, "pos");
private static final MethodAccessor GET_BUFFER = Accessors.getMethodAccessor(HANDLE_TYPE, "buffer");
public static final EquivalentConverter<WrappedChunkBiomeData> CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedChunkBiomeData specific) {
return CONSTRUCTOR.invoke(
ChunkCoordIntPair.getConverter().getGeneric(specific.position),
specific.buffer.clone());
}
@Override
public WrappedChunkBiomeData getSpecific(Object generic) {
return new WrappedChunkBiomeData(
ChunkCoordIntPair.getConverter().getSpecific(GET_POS.invoke(generic)),
((byte[]) GET_BUFFER.invoke(generic)).clone());
}
@Override
public Class<WrappedChunkBiomeData> getSpecificType() {
return WrappedChunkBiomeData.class;
}
});
private final ChunkCoordIntPair position;
private final byte[] buffer;
public WrappedChunkBiomeData(ChunkCoordIntPair position, byte[] buffer) {
this.position = position;
this.buffer = buffer.clone();
}
public ChunkCoordIntPair getPosition() {
return position;
}
public byte[] getBuffer() {
return buffer.clone();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedChunkBiomeData that)) {
return false;
}
return Objects.equals(position, that.position) && Arrays.equals(buffer, that.buffer);
}
@Override
public int hashCode() {
int result = Objects.hash(position);
result = 31 * result + Arrays.hashCode(buffer);
return result;
}
@Override
public String toString() {
return "WrappedChunkBiomeData{position=" + position + ", buffer=" + Arrays.toString(buffer) + '}';
}
}
@@ -1,140 +0,0 @@
package com.comphenix.protocol.wrappers;
import java.util.Objects;
import com.comphenix.protocol.events.InternalStructure;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Wraps the NMS {@code ExplosionParticleInfo} record, which pairs a
* {@link WrappedParticle} with scaling and speed float values.
*
* <p>NMS record: {@code ExplosionParticleInfo(ParticleOptions particle, float scaling, float speed)}
*/
public class WrappedExplosionParticleInfo {
private static final Class<?> NMS_CLASS;
private static final ConstructorAccessor NMS_CTOR;
static {
NMS_CLASS = MinecraftReflection.getMinecraftClass("core.particles.ExplosionParticleInfo");
NMS_CTOR = Accessors.getConstructorAccessor(
NMS_CLASS,
MinecraftReflection.getParticleParam(),
float.class,
float.class);
}
/**
* Returns the NMS {@code ExplosionParticleInfo} class.
*/
public static Class<?> getNmsClass() {
return NMS_CLASS;
}
// ── Instance data ────────────────────────────────────────────────────
private WrappedParticle<?> particle;
private float scaling;
private float speed;
public WrappedExplosionParticleInfo() {
this.scaling = 1.0f;
this.speed = 1.0f;
}
public WrappedExplosionParticleInfo(WrappedParticle<?> particle, float scaling, float speed) {
this.particle = particle;
this.scaling = scaling;
this.speed = speed;
}
public WrappedParticle<?> getParticle() {
return particle;
}
public void setParticle(WrappedParticle<?> particle) {
this.particle = particle;
}
public float getScaling() {
return scaling;
}
public void setScaling(float scaling) {
this.scaling = scaling;
}
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
}
// ── Converter ────────────────────────────────────────────────────────
/**
* Reads an NMS {@code ExplosionParticleInfo} into this wrapper via
* {@link InternalStructure} typed getters.
*/
public static WrappedExplosionParticleInfo fromHandle(InternalStructure handle) {
WrappedParticle<?> particle = handle.getNewParticles().read(0);
float scaling = handle.getFloat().read(0);
float speed = handle.getFloat().read(1);
return new WrappedExplosionParticleInfo(particle, scaling, speed);
}
/**
* Returns an {@link EquivalentConverter} between {@code WrappedExplosionParticleInfo}
* and the NMS {@code ExplosionParticleInfo} record.
*/
public static EquivalentConverter<WrappedExplosionParticleInfo> getConverter() {
return new EquivalentConverter<>() {
@Override
public WrappedExplosionParticleInfo getSpecific(Object generic) {
InternalStructure structure = InternalStructure.getConverter().getSpecific(generic);
return fromHandle(structure);
}
@Override
public Object getGeneric(WrappedExplosionParticleInfo specific) {
EquivalentConverter<WrappedParticle> particleConverter = BukkitConverters.getParticleConverter();
Object nmsParticle = particleConverter.getGeneric(specific.particle);
return NMS_CTOR.invoke(nmsParticle, specific.scaling, specific.speed);
}
@Override
public Class<WrappedExplosionParticleInfo> getSpecificType() {
return WrappedExplosionParticleInfo.class;
}
};
}
// ── Object methods ───────────────────────────────────────────────────
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof WrappedExplosionParticleInfo that)) return false;
return Float.compare(scaling, that.scaling) == 0
&& Float.compare(speed, that.speed) == 0
&& Objects.equals(particle, that.particle);
}
@Override
public int hashCode() {
return Objects.hash(particle, scaling, speed);
}
@Override
public String toString() {
return "WrappedExplosionParticleInfo{particle=" + particle
+ ", scaling=" + scaling + ", speed=" + speed + "}";
}
}
@@ -41,23 +41,16 @@ public class WrappedGameProfile extends AbstractWrapper {
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAccessorOrNull(
GAME_PROFILE, "id", String.class);
private static MethodAccessor GET_ID;
private static MethodAccessor GET_NAME;
private static MethodAccessor GET_PROPERTIES;
private static MethodAccessor IS_COMPLETE;
private static final MethodAccessor GET_ID = findMethodAccessor(GAME_PROFILE, "id", "getId");
private static final MethodAccessor GET_NAME = findMethodAccessor(GAME_PROFILE, "name", "getName");
private static final MethodAccessor GET_PROPERTIES = findMethodAccessor(
GAME_PROFILE, "properties", "getProperties");
private static final MethodAccessor IS_COMPLETE = findMethodAccessor(
GAME_PROFILE, "complete", "isComplete");
static {
GET_ID = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "getId");
if (GET_ID == null) {
GET_ID = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "id");
GET_NAME = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "name");
GET_PROPERTIES = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "properties");
IS_COMPLETE = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "complete");
} else {
GET_NAME = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "getName");
GET_PROPERTIES = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "getProperties");
IS_COMPLETE = Accessors.getMethodAccessorOrNull(GAME_PROFILE, "isComplete");
}
static MethodAccessor findMethodAccessor(Class<?> type, String methodName, String fallbackName) {
MethodAccessor accessor = Accessors.getMethodAccessorOrNull(type, methodName);
return accessor != null ? accessor : Accessors.getMethodAccessorOrNull(type, fallbackName);
}
// Fetching game profile
@@ -1,95 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.Objects;
/**
* Wrapper for {@code net.minecraft.server.packs.repository.KnownPack}.
*/
public final class WrappedKnownPack {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getMinecraftClass("server.packs.repository.KnownPack");
private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(
HANDLE_TYPE,
String.class,
String.class,
String.class);
private static final MethodAccessor GET_NAMESPACE = Accessors.getMethodAccessor(HANDLE_TYPE, "namespace");
private static final MethodAccessor GET_ID = Accessors.getMethodAccessor(HANDLE_TYPE, "id");
private static final MethodAccessor GET_VERSION = Accessors.getMethodAccessor(HANDLE_TYPE, "version");
public static final EquivalentConverter<WrappedKnownPack> CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedKnownPack specific) {
return CONSTRUCTOR.invoke(specific.namespace, specific.id, specific.version);
}
@Override
public WrappedKnownPack getSpecific(Object generic) {
return new WrappedKnownPack(
(String) GET_NAMESPACE.invoke(generic),
(String) GET_ID.invoke(generic),
(String) GET_VERSION.invoke(generic));
}
@Override
public Class<WrappedKnownPack> getSpecificType() {
return WrappedKnownPack.class;
}
});
private final String namespace;
private final String id;
private final String version;
public WrappedKnownPack(String namespace, String id, String version) {
this.namespace = namespace;
this.id = id;
this.version = version;
}
public String getNamespace() {
return namespace;
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedKnownPack that)) {
return false;
}
return Objects.equals(namespace, that.namespace)
&& Objects.equals(id, that.id)
&& Objects.equals(version, that.version);
}
@Override
public int hashCode() {
return Objects.hash(namespace, id, version);
}
@Override
public String toString() {
return "WrappedKnownPack{"
+ "namespace='" + namespace + '\''
+ ", id='" + id + '\''
+ ", version='" + version + '\''
+ '}';
}
}
@@ -1,96 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.BitSet;
import java.util.Objects;
/**
* Wrapper for {@code LastSeenMessages.Update}.
*/
public final class WrappedLastSeenMessagesUpdate {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getMinecraftClass("network.chat.LastSeenMessages$Update");
private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(
HANDLE_TYPE,
int.class,
BitSet.class,
byte.class);
private static final MethodAccessor GET_OFFSET = Accessors.getMethodAccessor(HANDLE_TYPE, "offset");
private static final MethodAccessor GET_ACKNOWLEDGED = Accessors.getMethodAccessor(HANDLE_TYPE, "acknowledged");
private static final MethodAccessor GET_CHECKSUM = Accessors.getMethodAccessor(HANDLE_TYPE, "checksum");
public static final EquivalentConverter<WrappedLastSeenMessagesUpdate> CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedLastSeenMessagesUpdate specific) {
return CONSTRUCTOR.invoke(specific.offset, specific.getAcknowledged(), specific.checksum);
}
@Override
public WrappedLastSeenMessagesUpdate getSpecific(Object generic) {
return new WrappedLastSeenMessagesUpdate(
(int) GET_OFFSET.invoke(generic),
(BitSet) GET_ACKNOWLEDGED.invoke(generic),
(byte) GET_CHECKSUM.invoke(generic));
}
@Override
public Class<WrappedLastSeenMessagesUpdate> getSpecificType() {
return WrappedLastSeenMessagesUpdate.class;
}
});
private final int offset;
private final BitSet acknowledged;
private final byte checksum;
public WrappedLastSeenMessagesUpdate(int offset, BitSet acknowledged, byte checksum) {
this.offset = offset;
this.acknowledged = (BitSet) acknowledged.clone();
this.checksum = checksum;
}
public int getOffset() {
return offset;
}
public BitSet getAcknowledged() {
return (BitSet) acknowledged.clone();
}
public byte getChecksum() {
return checksum;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedLastSeenMessagesUpdate that)) {
return false;
}
return offset == that.offset
&& checksum == that.checksum
&& Objects.equals(acknowledged, that.acknowledged);
}
@Override
public int hashCode() {
return Objects.hash(offset, acknowledged, checksum);
}
@Override
public String toString() {
return "WrappedLastSeenMessagesUpdate{"
+ "offset=" + offset
+ ", acknowledged=" + acknowledged
+ ", checksum=" + checksum
+ '}';
}
}
@@ -3,8 +3,11 @@ package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import com.comphenix.protocol.reflect.EquivalentConverter;
import org.jetbrains.annotations.Nullable;
@@ -49,11 +52,19 @@ public final class WrappedLevelChunkData {
static {
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(reflection.getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(MinecraftReflection.getPacketDataSerializerClass())
.parameterExactType(int.class)
.parameterExactType(int.class)
.build()));
if (MinecraftVersion.v26_3.atOrAbove()) {
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(reflection.getConstructor(FuzzyMethodContract.newBuilder()
.parameterExactType(Map.class)
.parameterExactType(byte[].class)
.parameterExactType(List.class)
.build()));
} else {
LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR = Accessors.getConstructorAccessor(reflection.getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(MinecraftReflection.getPacketDataSerializerClass())
.parameterExactType(int.class)
.parameterExactType(int.class)
.build()));
}
BLOCK_ENTITIES_DATA_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(List.class)
.build()));
@@ -180,7 +191,12 @@ public final class WrappedLevelChunkData {
*/
@Deprecated
public static ChunkData fromValues(NbtCompound heightmapsTag, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
ChunkData data;
if (MinecraftVersion.v26_3.atOrAbove()) {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(Collections.emptyMap(), new byte[0], Collections.emptyList()));
} else {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
}
data.setHeightmapsTag(heightmapsTag);
data.setBuffer(buffer);
@@ -198,7 +214,12 @@ public final class WrappedLevelChunkData {
* @return a newly created wrapper
*/
public static ChunkData fromValues(Map<EnumWrappers.HeightmapType, long[]> heightmaps, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
ChunkData data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
ChunkData data;
if (MinecraftVersion.v26_3.atOrAbove()) {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(Collections.emptyMap(), new byte[0], Collections.emptyList()));
} else {
data = new ChunkData(LEVEL_CHUNK_PACKET_DATA_CONSTRUCTOR.invoke(StructureCache.newNullDataSerializer(), 0, 0));
}
data.setHeightmaps(heightmaps);
data.setBuffer(buffer);
@@ -433,18 +454,29 @@ public final class WrappedLevelChunkData {
static {
FuzzyReflection reflection = FuzzyReflection.fromClass(HANDLE_TYPE, true);
List<Field> posFields = reflection.getFieldList(FuzzyFieldContract.newBuilder().typeExact(int.class).build());
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, int.class, int.class,
MinecraftReflection.getBlockEntityTypeClass(), MinecraftReflection.getNBTCompoundClass());
PACKED_XZ_ACCESSOR = Accessors.getFieldAccessor(posFields.get(0));
Y_ACCESSOR = Accessors.getFieldAccessor(posFields.get(1));
if (MinecraftVersion.v26_3.atOrAbove()) {
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, byte.class, short.class,
MinecraftReflection.getBlockEntityTypeClass(), Optional.class);
PACKED_XZ_ACCESSOR = new IntegerFieldAccessor(Accessors.getFieldAccessor(HANDLE_TYPE, byte.class, true), Number::byteValue);
Y_ACCESSOR = new IntegerFieldAccessor(Accessors.getFieldAccessor(HANDLE_TYPE, short.class, true), Number::shortValue);
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(Optional.class)
.build()));
} else {
List<Field> posFields = reflection.getFieldList(FuzzyFieldContract.newBuilder().typeExact(int.class).build());
BLOCK_ENTITY_INFO_CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, int.class, int.class,
MinecraftReflection.getBlockEntityTypeClass(), MinecraftReflection.getNBTCompoundClass());
PACKED_XZ_ACCESSOR = Accessors.getFieldAccessor(posFields.get(0));
Y_ACCESSOR = Accessors.getFieldAccessor(posFields.get(1));
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getNBTCompoundClass())
.build()));
}
TYPE_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getBlockEntityTypeClass())
.build()));
TAG_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getNBTCompoundClass())
.build()));
}
public BlockEntityInfo(Object handle) {
@@ -533,6 +565,9 @@ public final class WrappedLevelChunkData {
@Nullable
public NbtCompound getAdditionalData() {
Object tagHandle = TAG_ACCESSOR.get(handle);
if (MinecraftVersion.v26_3.atOrAbove()) {
tagHandle = ((Optional<Object>) tagHandle).orElse(null);
}
return tagHandle == null ? null : NbtFactory.fromNMSCompound(tagHandle);
}
@@ -543,7 +578,11 @@ public final class WrappedLevelChunkData {
* @param additionalData the additional data for this block entity, can be {@code null}
*/
public void setAdditionalData(@Nullable NbtCompound additionalData) {
TAG_ACCESSOR.set(handle, additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle());
if (MinecraftVersion.v26_3.atOrAbove()) {
TAG_ACCESSOR.set(handle, Optional.ofNullable(additionalData).map(data -> NbtFactory.fromBase(data).getHandle()));
} else {
TAG_ACCESSOR.set(handle, additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle());
}
}
/**
@@ -568,12 +607,46 @@ public final class WrappedLevelChunkData {
* @param additionalData An NBT-Tag containing additional information. Can be {@code null}.
*/
public static BlockEntityInfo fromValues(int sectionX, int sectionZ, int y, MinecraftKey typeKey, @Nullable NbtCompound additionalData) {
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
if (MinecraftVersion.v26_3.atOrAbove()) {
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
(byte) (sectionX << 4 | sectionZ),
(short) y,
REGISTRY.get(typeKey),
Optional.ofNullable(additionalData).map(data -> NbtFactory.fromBase(data).getHandle())
));
} else {
return new BlockEntityInfo(BLOCK_ENTITY_INFO_CONSTRUCTOR.invoke(
sectionX << 4 | sectionZ,
y,
REGISTRY.get(typeKey),
additionalData == null ? null : NbtFactory.fromBase(additionalData).getHandle()
));
));
}
}
}
private final static class IntegerFieldAccessor implements FieldAccessor {
private final FieldAccessor inner;
private final Function<Number, Object> toType;
public IntegerFieldAccessor(FieldAccessor inner, Function<Number, Object> toType) {
this.inner = inner;
this.toType = toType;
}
@Override
public Object get(Object instance) {
return ((Number) this.inner.get(instance)).intValue();
}
@Override
public void set(Object instance, Object value) {
this.inner.set(instance, this.toType.apply((Integer) value));
}
@Override
public Field getField() {
return this.inner.getField();
}
}
}
@@ -1,126 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import org.bukkit.util.Vector;
import java.util.Objects;
/**
* Wrapper for {@code NewMinecartBehavior.MinecartStep}.
*/
public final class WrappedMinecartStep {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getMinecraftClass(
"world.entity.vehicle.minecart.NewMinecartBehavior$MinecartStep");
private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(
HANDLE_TYPE,
MinecraftReflection.getVec3DClass(),
MinecraftReflection.getVec3DClass(),
float.class,
float.class,
float.class);
private static final MethodAccessor GET_POSITION = Accessors.getMethodAccessor(HANDLE_TYPE, "position");
private static final MethodAccessor GET_MOVEMENT = Accessors.getMethodAccessor(HANDLE_TYPE, "movement");
private static final MethodAccessor GET_Y_ROT = Accessors.getMethodAccessor(HANDLE_TYPE, "yRot");
private static final MethodAccessor GET_X_ROT = Accessors.getMethodAccessor(HANDLE_TYPE, "xRot");
private static final MethodAccessor GET_WEIGHT = Accessors.getMethodAccessor(HANDLE_TYPE, "weight");
public static final EquivalentConverter<WrappedMinecartStep> CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedMinecartStep specific) {
EquivalentConverter<Vector> vectorConverter = BukkitConverters.getVectorConverter();
return CONSTRUCTOR.invoke(
vectorConverter.getGeneric(specific.position),
vectorConverter.getGeneric(specific.movement),
specific.yRot,
specific.xRot,
specific.weight);
}
@Override
public WrappedMinecartStep getSpecific(Object generic) {
EquivalentConverter<Vector> vectorConverter = BukkitConverters.getVectorConverter();
return new WrappedMinecartStep(
vectorConverter.getSpecific(GET_POSITION.invoke(generic)),
vectorConverter.getSpecific(GET_MOVEMENT.invoke(generic)),
(float) GET_Y_ROT.invoke(generic),
(float) GET_X_ROT.invoke(generic),
(float) GET_WEIGHT.invoke(generic));
}
@Override
public Class<WrappedMinecartStep> getSpecificType() {
return WrappedMinecartStep.class;
}
});
private final Vector position;
private final Vector movement;
private final float yRot;
private final float xRot;
private final float weight;
public WrappedMinecartStep(Vector position, Vector movement, float yRot, float xRot, float weight) {
this.position = position.clone();
this.movement = movement.clone();
this.yRot = yRot;
this.xRot = xRot;
this.weight = weight;
}
public Vector getPosition() {
return position.clone();
}
public Vector getMovement() {
return movement.clone();
}
public float getYRot() {
return yRot;
}
public float getXRot() {
return xRot;
}
public float getWeight() {
return weight;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedMinecartStep that)) {
return false;
}
return Float.compare(yRot, that.yRot) == 0
&& Float.compare(xRot, that.xRot) == 0
&& Float.compare(weight, that.weight) == 0
&& Objects.equals(position, that.position)
&& Objects.equals(movement, that.movement);
}
@Override
public int hashCode() {
return Objects.hash(position, movement, yRot, xRot, weight);
}
@Override
public String toString() {
return "WrappedMinecartStep{"
+ "position=" + position
+ ", movement=" + movement
+ ", yRot=" + yRot
+ ", xRot=" + xRot
+ ", weight=" + weight
+ '}';
}
}
@@ -27,6 +27,15 @@ public class WrappedParticle<T> {
private static MethodAccessor toNMS;
private static MethodAccessor toCraftData;
private static final class ItemStackTemplateAccessor {
private static final Class<?> TYPE = MinecraftReflection.getMinecraftClass("world.item.ItemStackTemplate");
private static final MethodAccessor CREATE_ITEM_STACK = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(TYPE).getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(MinecraftReflection.getItemStackClass())
.parameterCount(0)
.build()));
}
private static void ensureMethods() {
if (toBukkit != null && toNMS != null) {
return;
@@ -176,6 +185,15 @@ public class WrappedParticle<T> {
}
private static Object getItem(Object handle) {
if (MinecraftVersion.v26_1.atOrAbove()) {
Object template = new StructureModifier<>(handle.getClass())
.withTarget(handle)
.withType(ItemStackTemplateAccessor.TYPE)
.read(0);
Object itemStack = ItemStackTemplateAccessor.CREATE_ITEM_STACK.invoke(template);
return BukkitConverters.getItemStackConverter().getSpecific(itemStack);
}
return new StructureModifier<>(handle.getClass())
.withTarget(handle)
.withType(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter())
@@ -0,0 +1,207 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Preconditions;
import org.bukkit.util.Vector;
/**
* Wrapper around the NMS {@code PositionPath} type, introduced in Minecraft 26.3.
* <p>
* Used by {@code ENTITY_POSITION_SYNC} to store the entity position. A path is either linear (a single position) or
* stepped (several positions reached at given ticks).
* <pre>{@code
* packet.getPositionPaths().write(0, WrappedPositionPath.linear(new Vector(x, y, z)));
* }</pre>
*/
public final class WrappedPositionPath extends AbstractWrapper {
private static Class<?> STEPPED_CLASS;
private static ConstructorAccessor LINEAR_CONSTRUCTOR;
private static ConstructorAccessor STEPPED_CONSTRUCTOR;
private static ConstructorAccessor STEP_CONSTRUCTOR;
private static StructureModifier<Object> LINEAR_MODIFIER;
private static StructureModifier<Object> STEPPED_MODIFIER;
private static StructureModifier<Object> STEP_MODIFIER;
private static volatile boolean initialized;
private WrappedPositionPath(Object handle) {
super(getPositionPathClass());
setHandle(handle);
}
private static Class<?> getPositionPathClass() {
return MinecraftReflection.getPositionPathClass()
.orElseThrow(() -> new UnsupportedOperationException("PositionPath requires Minecraft 26.3 or newer"));
}
private static void initialize() {
if (initialized) {
return;
}
Class<?> pathClass = getPositionPathClass();
Class<?> vec3Class = MinecraftReflection.getVec3DClass();
Class<?> linearClass = null;
Class<?> stepClass = null;
for (Class<?> nested : pathClass.getDeclaredClasses()) {
if (!pathClass.isAssignableFrom(nested)) {
continue;
}
Field steps = getListField(nested);
if (steps != null) {
STEPPED_CLASS = nested;
stepClass = (Class<?>) ((ParameterizedType) steps.getGenericType()).getActualTypeArguments()[0];
} else {
linearClass = nested;
}
}
Preconditions.checkState(linearClass != null && STEPPED_CLASS != null, "Unable to find PositionPath implementations");
LINEAR_CONSTRUCTOR = Accessors.getConstructorAccessor(linearClass, vec3Class);
STEPPED_CONSTRUCTOR = Accessors.getConstructorAccessor(STEPPED_CLASS, List.class);
STEP_CONSTRUCTOR = Accessors.getConstructorAccessor(stepClass, vec3Class, int.class);
LINEAR_MODIFIER = new StructureModifier<>(linearClass);
STEPPED_MODIFIER = new StructureModifier<>(STEPPED_CLASS);
STEP_MODIFIER = new StructureModifier<>(stepClass);
initialized = true;
}
private static Field getListField(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (List.class.equals(field.getType())) {
return field;
}
}
return null;
}
public static WrappedPositionPath fromHandle(Object handle) {
return new WrappedPositionPath(handle);
}
/**
* Creates a linear path.
*
* @param position - the absolute position.
* @return the path.
*/
public static WrappedPositionPath linear(Vector position) {
Preconditions.checkNotNull(position, "position cannot be NULL");
initialize();
return new WrappedPositionPath(LINEAR_CONSTRUCTOR.invoke(BukkitConverters.getVectorConverter().getGeneric(position)));
}
/**
* Creates a stepped path ending at the position of the last step.
*
* @param steps - the steps in order, must not be empty.
* @return the path.
*/
public static WrappedPositionPath stepped(List<Step> steps) {
Preconditions.checkArgument(steps != null && !steps.isEmpty(), "steps cannot be empty");
initialize();
EquivalentConverter<Vector> vectorConverter = BukkitConverters.getVectorConverter();
List<Object> handles = new ArrayList<>(steps.size());
for (Step step : steps) {
handles.add(STEP_CONSTRUCTOR.invoke(vectorConverter.getGeneric(step.getPosition()), step.getTickOffset()));
}
return new WrappedPositionPath(STEPPED_CONSTRUCTOR.invoke(Collections.unmodifiableList(handles)));
}
public boolean isStepped() {
initialize();
return STEPPED_CLASS.isInstance(handle);
}
/**
* @return the final position of the path.
*/
public Vector getEndPosition() {
StructureModifier<Object> modifier = isStepped() ? STEPPED_MODIFIER : LINEAR_MODIFIER;
return modifier.withTarget(handle)
.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter())
.read(0);
}
/**
* @return the steps of a stepped path.
* @throws IllegalStateException if the path is linear.
*/
public List<Step> getSteps() {
Preconditions.checkState(isStepped(), "Linear paths have no steps");
List<?> handles = STEPPED_MODIFIER.withTarget(handle).<List<?>>withType(List.class).read(0);
List<Step> steps = new ArrayList<>(handles.size());
for (Object stepHandle : handles) {
StructureModifier<Object> step = STEP_MODIFIER.withTarget(stepHandle);
Vector position = step.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
steps.add(new Step(position, step.<Integer>withType(int.class).read(0)));
}
return Collections.unmodifiableList(steps);
}
public static EquivalentConverter<WrappedPositionPath> getConverter() {
return Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle,
WrappedPositionPath::fromHandle, WrappedPositionPath.class));
}
public static final class Step {
private final Vector position;
private final int tickOffset;
public Step(Vector position, int tickOffset) {
this.position = Preconditions.checkNotNull(position, "position cannot be NULL").clone();
this.tickOffset = tickOffset;
}
public Vector getPosition() {
return position.clone();
}
public int getTickOffset() {
return tickOffset;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Step)) return false;
Step that = (Step) obj;
return tickOffset == that.tickOffset && position.equals(that.position);
}
@Override
public int hashCode() {
return Objects.hash(position, tickOffset);
}
@Override
public String toString() {
return "Step[position=" + position + ", tickOffset=" + tickOffset + "]";
}
}
}
@@ -1,48 +1,28 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftRegistryAccess;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class WrappedRegistry {
// map of NMS class to registry instance (static/built-in registries)
// map of NMS class to registry instance
private static final Map<Class<?>, WrappedRegistry> REGISTRY;
// map of NMS class to ResourceKey for dynamic (datapack) registries
private static final Map<Class<?>, Object> DYNAMIC_REGISTRY_KEYS;
// ResourceKeys to try when generic metadata is unavailable or incomplete
private static final Set<Object> DYNAMIC_REGISTRY_FALLBACK_KEYS;
// cache of dynamically resolved registries
private static final Map<Class<?>, WrappedRegistry> DYNAMIC_REGISTRY_CACHE = new ConcurrentHashMap<>();
private static final Map<Object, WrappedRegistry> DYNAMIC_REGISTRY_KEY_CACHE = new ConcurrentHashMap<>();
// RegistryAccess.lookup(ResourceKey) -> Optional<Registry>
private static final MethodAccessor REGISTRY_ACCESS_LOOKUP;
private static final MethodAccessor GET;
private static final MethodAccessor GET_ID;
private static final MethodAccessor GET_KEY;
@@ -98,69 +78,13 @@ public class WrappedRegistry {
}
}
}
} catch (ReflectiveOperationException ex) {
ProtocolLogger.debug("Failed to inspect built-in registry field " + field, ex);
} catch (ReflectiveOperationException ignored) {
}
}
}
REGISTRY = ImmutableMap.copyOf(regMap);
// Build dynamic registry key map from Registries class (ResourceKey<Registry<T>> fields)
// These cover datapack registries like DamageType that are not in BuiltInRegistries
Map<Class<?>, Object> dynKeyMap = new HashMap<>();
// LinkedHashSet so the generics-stripped fallback iterates in declaration order
// on Registries, giving deterministic resolution across JVM runs.
Set<Object> dynKeyFallbacks = new LinkedHashSet<>();
try {
Class<?> registriesClass = MinecraftReflection.getNullableNMS("core.registries.Registries");
Class<?> resourceKeyClass = MinecraftReflection.getResourceKey();
if (registriesClass != null) {
for (Field field : registriesClass.getFields()) {
if (!Modifier.isStatic(field.getModifiers())) continue;
if (!resourceKeyClass.isAssignableFrom(field.getType())) continue;
Object registryKey;
try {
registryKey = field.get(null);
} catch (ReflectiveOperationException ex) {
ProtocolLogger.debug("Failed to read dynamic registry key field " + field, ex);
continue;
}
dynKeyFallbacks.add(registryKey);
// looking for ResourceKey<Registry<T>> fields
Class<?> entryClass = getRegistryEntryClass(field, iRegistry);
if (entryClass != null && !regMap.containsKey(entryClass)) {
dynKeyMap.put(entryClass, registryKey);
}
}
}
} catch (Exception ex) {
ProtocolLogger.debug("Failed to inspect dynamic registry keys", ex);
}
DYNAMIC_REGISTRY_KEYS = ImmutableMap.copyOf(dynKeyMap);
DYNAMIC_REGISTRY_FALLBACK_KEYS = Collections.unmodifiableSet(dynKeyFallbacks);
// Set up RegistryAccess.lookup(ResourceKey) -> Optional<Registry>
MethodAccessor registryAccessLookup = null;
try {
Class<?> registryAccessClass = MinecraftReflection.getRegistryAccessClass();
if (registryAccessClass != null) {
registryAccessLookup = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(registryAccessClass, false).getMethod(
FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getResourceKey())
.returnTypeExact(Optional.class)
.build()
)
);
}
} catch (Exception ex) {
ProtocolLogger.debug("Failed to find RegistryAccess.lookup(ResourceKey)", ex);
}
REGISTRY_ACCESS_LOOKUP = registryAccessLookup;
FuzzyReflection fuzzy = FuzzyReflection.fromClass(iRegistry, false);
GET = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
@@ -193,53 +117,17 @@ public class WrappedRegistry {
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.PUBLIC)
.build()));
} catch (IllegalArgumentException ex) {
ProtocolLogger.debug("Failed to find Registry.getHolder method", ex);
} catch (IllegalArgumentException ignored) {
}
}
GET_HOLDER = getHolder;
}
@Nullable
private static Class<?> getRegistryEntryClass(Field field, Class<?> iRegistry) {
Type genType = field.getGenericType();
if (!(genType instanceof ParameterizedType)) return null;
ParameterizedType outer = (ParameterizedType) genType;
if (outer.getActualTypeArguments().length != 1) return null;
Type innerType = outer.getActualTypeArguments()[0];
if (!(innerType instanceof ParameterizedType)) return null;
ParameterizedType registryType = (ParameterizedType) innerType;
if (!(registryType.getRawType() instanceof Class<?>)) return null;
Class<?> rawRegistry = (Class<?>) registryType.getRawType();
if (iRegistry != null && !iRegistry.isAssignableFrom(rawRegistry)) return null;
if (registryType.getActualTypeArguments().length != 1) return null;
Type entryType = registryType.getActualTypeArguments()[0];
if (entryType instanceof Class<?>) {
return (Class<?>) entryType;
} else if (entryType instanceof ParameterizedType) {
Type raw = ((ParameterizedType) entryType).getRawType();
if (raw instanceof Class<?>) return (Class<?>) raw;
}
return null;
}
private final Object handle;
/** The NMS {@code ResourceKey<Registry<T>>} for this registry, if known. */
@Nullable
private final Object registryKey;
private WrappedRegistry(Object handle) {
this(handle, null);
}
private WrappedRegistry(Object handle, @Nullable Object registryKey) {
this.handle = handle;
this.registryKey = registryKey;
}
public Object get(MinecraftKey key) {
@@ -267,10 +155,6 @@ public class WrappedRegistry {
}
public Object getHolder(Object generic) {
if (GET_HOLDER == null) {
throw new UnsupportedOperationException(
"Registry holder conversion is not available on this Minecraft version");
}
return GET_HOLDER.invoke(handle, generic);
}
@@ -286,193 +170,7 @@ public class WrappedRegistry {
return getRegistry(MinecraftReflection.getSoundEffectClass());
}
// Sentinel for negative dynamic-registry lookups. ConcurrentHashMap forbids null
// values, so we cache this to avoid re-walking every fallback registry on every miss.
private static final WrappedRegistry MISS_SENTINEL = new WrappedRegistry(new Object());
public static WrappedRegistry getRegistry(Class<?> type) {
WrappedRegistry registry = REGISTRY.get(type);
if (registry != null) {
return registry;
}
// Fall back to dynamic (datapack) registry lookup via RegistryAccess
WrappedRegistry cached = DYNAMIC_REGISTRY_CACHE.computeIfAbsent(type, t -> {
Object resourceKey = DYNAMIC_REGISTRY_KEYS.get(t);
WrappedRegistry dynamicRegistry = lookupDynamicRegistry(resourceKey);
if (dynamicRegistry != null) {
return dynamicRegistry;
}
WrappedRegistry byValueType = findDynamicRegistryByValueType(t);
return byValueType != null ? byValueType : MISS_SENTINEL;
});
return cached == MISS_SENTINEL ? null : cached;
}
@Nullable
private static WrappedRegistry findDynamicRegistryByValueType(Class<?> type) {
for (Object resourceKey : DYNAMIC_REGISTRY_FALLBACK_KEYS) {
WrappedRegistry registry = lookupDynamicRegistry(resourceKey);
if (registry != null && registryContainsValueType(registry.handle, type)) {
return registry;
}
}
return null;
}
private static boolean registryContainsValueType(Object registryHandle, Class<?> type) {
if (!(registryHandle instanceof Iterable<?> iterable)) {
return false;
}
for (Object entry : iterable) {
if (type.isInstance(entry)) {
return true;
}
}
return false;
}
@Nullable
private static WrappedRegistry lookupDynamicRegistry(@Nullable Object resourceKey) {
if (resourceKey == null || REGISTRY_ACCESS_LOOKUP == null) {
return null;
}
return DYNAMIC_REGISTRY_KEY_CACHE.computeIfAbsent(resourceKey, key -> {
Object registryAccess = MinecraftRegistryAccess.get();
if (registryAccess == null) {
return null;
}
Optional<?> optRegistry = (Optional<?>) REGISTRY_ACCESS_LOOKUP.invoke(registryAccess, key);
if (optRegistry == null || optRegistry.isEmpty()) {
return null;
}
return new WrappedRegistry(optRegistry.get(), key);
});
}
// -------------------------------------------------------------------------
// ResourceKey helpers
// -------------------------------------------------------------------------
/**
* Returns the NMS {@code ResourceKey<Registry<T>>} that identifies this registry,
* or {@code null} if it is not known (e.g. for built-in registries resolved only
* by type scanning).
*/
@Nullable
public Object getRegistryKey() {
return registryKey;
}
/**
* Converts a {@link MinecraftKey} entry location to the corresponding NMS
* {@code ResourceKey<T>} scoped to this registry.
*
* @param key the entry identifier (e.g. {@code minecraft:diamond})
* @return the NMS {@code ResourceKey<T>} handle
* @throws IllegalStateException if this registry's own key is not known
*/
public Object toResourceKey(MinecraftKey key) {
if (registryKey == null) {
throw new IllegalStateException("Registry key is not known for this WrappedRegistry instance");
}
return WrappedResourceKey.of(registryKey, key).getHandle();
}
/**
* Converts a NMS {@code ResourceKey<T>} back to the {@link MinecraftKey}
* representing the entry location.
*
* @param nmsResourceKey the NMS {@code ResourceKey} object
* @return the entry identifier
*/
public static MinecraftKey fromResourceKey(Object nmsResourceKey) {
return WrappedResourceKey.fromHandle(nmsResourceKey).getLocation();
}
/**
* Returns an {@link EquivalentConverter} between
* {@link MinecraftKey} and NMS {@code ResourceKey<T>} for this registry.
*
* @throws IllegalStateException if this registry's own key is not known
*/
public EquivalentConverter<MinecraftKey> resourceKeyConverter() {
if (registryKey == null) {
throw new IllegalStateException("Registry key is not known for this WrappedRegistry instance");
}
return WrappedResourceKey.converterFor(registryKey);
}
/**
* Returns an {@link EquivalentConverter} between
* {@link MinecraftKey} and the NMS registry value type {@code T} (e.g. {@code MenuType<?>},
* {@code SoundEvent}). The converter resolves both directions through this registry's
* {@code get(ResourceLocation)} / {@code getKey(value)} methods.
*
* <p>Use this for fields whose declared NMS type is the registry value itself, as opposed
* to {@code ResourceKey<T>} (use {@link #resourceKeyConverter()}) or {@code Holder<T>}
* (use {@link Converters#holder}).
*/
public EquivalentConverter<MinecraftKey> valueConverter() {
final WrappedRegistry self = this;
return new EquivalentConverter<>() {
@Override
public Object getGeneric(MinecraftKey specific) {
return specific == null ? null : self.get(specific);
}
@Override
public MinecraftKey getSpecific(Object generic) {
return generic == null ? null : self.getKey(generic);
}
@Override
public Class<MinecraftKey> getSpecificType() {
return MinecraftKey.class;
}
};
}
// -------------------------------------------------------------------------
// Named registry accessors
// -------------------------------------------------------------------------
/**
* Returns a {@link WrappedRegistry} backed by the NMS {@code GameRule} registry
* ({@code Registries.GAME_RULE}). The returned instance supports
* {@link #toResourceKey(MinecraftKey)} and {@link #resourceKeyConverter()}.
*
* @return the game-rule registry, or {@code null} if unavailable at this time
*/
@Nullable
public static WrappedRegistry getGameRuleRegistry() {
return getRegistryByNmsKey("core.registries.Registries", "GAME_RULE");
}
/**
* Looks up a registry by the name of a static {@code ResourceKey<Registry<T>>}
* field on the NMS {@code Registries} class.
*
* @param registriesClassName the NMS class name (e.g. {@code "core.registries.Registries"})
* @param fieldName the field name on that class (e.g. {@code "GAME_RULE"})
* @return a {@link WrappedRegistry} with both the handle and the registry key set,
* or {@code null} if the lookup fails
*/
@Nullable
public static WrappedRegistry getRegistryByNmsKey(String registriesClassName, String fieldName) {
try {
Class<?> registriesClass = MinecraftReflection.getMinecraftClass(registriesClassName);
Object regKey = registriesClass.getField(fieldName).get(null);
return lookupDynamicRegistry(regKey);
} catch (Exception ex) {
ProtocolLogger.debug("Failed to resolve registry key " + registriesClassName + "." + fieldName, ex);
}
return null;
return REGISTRY.get(type);
}
}
@@ -1,168 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Wrapper around the NMS {@code ResourceKey<T>} class.
*
* <p>A {@code ResourceKey} pairs a registry identifier (e.g. {@code minecraft:item})
* with an entry location (e.g. {@code minecraft:diamond}) to form a fully-qualified
* reference into a Minecraft registry.
*
* <p>All reflective lookups are done with {@link FuzzyReflection} so they are
* independent of Mojang vs. Yarn/intermediary method names.
*/
public final class WrappedResourceKey {
// ResourceKey#location() / ResourceKey#identifier() → ResourceLocation
private static final MethodAccessor GET_LOCATION;
// static ResourceKey#create(ResourceKey<Registry<T>>, ResourceLocation) → ResourceKey<T>
private static final MethodAccessor CREATE;
static {
Class<?> resourceKeyClass = MinecraftReflection.getResourceKey();
Class<?> resourceLocClass = MinecraftReflection.getMinecraftKeyClass();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(resourceKeyClass, false);
// Resolve location() / identifier() by name first (mapping-specific names are known),
// then fall back to fuzzy if neither exists. We must NOT pick registry() by accident —
// that also returns ResourceLocation but is the registry namespace, not the entry path.
MethodAccessor getLocation = null;
for (String candidate : new String[]{"location", "identifier", "getLocation"}) {
try {
Method m = resourceKeyClass.getMethod(candidate);
if (m.getReturnType().equals(resourceLocClass)) {
getLocation = Accessors.getMethodAccessor(m);
break;
}
} catch (NoSuchMethodException ignored) {
}
}
if (getLocation == null) {
// Last-resort fuzzy: pick the method whose simple name contains "location" or "identifier"
getLocation = Accessors.getMethodAccessor(
fuzzy.getMethodList(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.banModifier(Modifier.STATIC)
.returnTypeExact(resourceLocClass)
.build())
.stream()
.filter(m -> {
String n = m.getName().toLowerCase();
return n.contains("location") || n.contains("identifier");
})
.findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find ResourceKey#location() method"))
);
}
GET_LOCATION = getLocation;
// Find the static two-arg factory: create(ResourceKey<Registry<T>>, ResourceLocation)
CREATE = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(2)
.requireModifier(Modifier.STATIC)
.parameterExactArray(resourceKeyClass, resourceLocClass)
.returnTypeExact(resourceKeyClass)
.build()));
}
private final Object handle;
private WrappedResourceKey(Object handle) {
this.handle = handle;
}
// -------------------------------------------------------------------------
// Factory / unwrap
// -------------------------------------------------------------------------
/**
* Wraps an existing NMS {@code ResourceKey} handle.
*
* @param handle the NMS object; must be an instance of {@code ResourceKey}
* @return a new wrapper
*/
public static WrappedResourceKey fromHandle(Object handle) {
if (handle == null) throw new NullPointerException("handle");
return new WrappedResourceKey(handle);
}
/**
* Creates a new {@code ResourceKey<T>} pointing into the given registry.
*
* @param registryKey the NMS {@code ResourceKey<Registry<T>>} that identifies the registry
* (e.g. the value of {@code Registries.ITEM})
* @param location the entry identifier within that registry
* @return a wrapper around the newly created {@code ResourceKey<T>}
*/
public static WrappedResourceKey of(Object registryKey, MinecraftKey location) {
Object resourceLocation = MinecraftKey.getConverter().getGeneric(location);
return new WrappedResourceKey(CREATE.invoke(null, registryKey, resourceLocation));
}
/**
* Returns the raw NMS {@code ResourceKey} handle.
*/
public Object getHandle() {
return handle;
}
// -------------------------------------------------------------------------
// Accessors
// -------------------------------------------------------------------------
/**
* Returns the entry location part of this key (e.g. {@code minecraft:diamond}).
* This is the {@code ResourceLocation} / {@code Identifier} component, NOT the
* registry identifier.
*/
public MinecraftKey getLocation() {
Object resourceLocation = GET_LOCATION.invoke(handle);
return MinecraftKey.getConverter().getSpecific(resourceLocation);
}
// -------------------------------------------------------------------------
// EquivalentConverter factory
// -------------------------------------------------------------------------
/**
* Returns an {@link EquivalentConverter} that converts
* between {@link MinecraftKey} (the entry location) and a NMS
* {@code ResourceKey<T>} belonging to the supplied registry key.
*
* @param registryKey the NMS {@code ResourceKey<Registry<T>>} (e.g. {@code Registries.ITEM})
*/
public static EquivalentConverter<MinecraftKey> converterFor(Object registryKey) {
return new EquivalentConverter<MinecraftKey>() {
@Override
public MinecraftKey getSpecific(Object generic) {
return WrappedResourceKey.fromHandle(generic).getLocation();
}
@Override
public Object getGeneric(MinecraftKey specific) {
return WrappedResourceKey.of(registryKey, specific).getHandle();
}
@Override
public Class<MinecraftKey> getSpecificType() {
return MinecraftKey.class;
}
};
}
@Override
public String toString() {
return "WrappedResourceKey{" + handle + "}";
}
}
@@ -16,15 +16,26 @@ import com.comphenix.protocol.utility.MinecraftReflection;
public class WrappedStatistic extends AbstractWrapper {
private static final Class<?> STATISTIC = MinecraftReflection.getStatisticClass();
private static final Class<?> STATISTIC_LIST = MinecraftReflection.getStatisticListClass();
private static final FieldAccessor GET_NAME_FIELD;
private static final MethodAccessor GET_NAME_METHOD;
static {
FieldAccessor[] nameFields = Accessors.getFieldAccessorArray(STATISTIC, String.class, true);
if (nameFields.length > 0) {
GET_NAME_FIELD = nameFields[0];
GET_NAME_METHOD = null;
} else {
GET_NAME_FIELD = null;
GET_NAME_METHOD = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(STATISTIC.getSuperclass(), true)
.getMethodByReturnTypeAndParameters("getName", String.class));
}
try {
FIND_STATISTICS = Accessors.getMethodAccessor(
FuzzyReflection.fromClass(STATISTIC_LIST).getMethodByReturnTypeAndParameters(
"findStatistic", STATISTIC, new Class<?>[]{String.class}));
MAP_ACCESSOR = Accessors.getFieldAccessor(STATISTIC_LIST, Map.class, true);
GET_NAME = Accessors.getFieldAccessor(STATISTIC, String.class, true);
} catch (Exception ex) {
// TODO - find an alternative
}
@@ -32,7 +43,6 @@ public class WrappedStatistic extends AbstractWrapper {
private static MethodAccessor FIND_STATISTICS;
private static FieldAccessor MAP_ACCESSOR;
private static FieldAccessor GET_NAME;
private final String name;
@@ -40,7 +50,9 @@ public class WrappedStatistic extends AbstractWrapper {
super(STATISTIC);
setHandle(handle);
this.name = (String) GET_NAME.get(handle);
this.name = GET_NAME_FIELD != null
? (String) GET_NAME_FIELD.get(handle)
: (String) GET_NAME_METHOD.invoke(handle);
}
/**
@@ -1,122 +0,0 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Wrapper for {@code TagNetworkSerialization.NetworkPayload}.
*/
public final class WrappedTagPayload {
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getMinecraftClass(
"tags.TagNetworkSerialization$NetworkPayload");
private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(HANDLE_TYPE, Map.class);
private static final FieldAccessor TAGS = Accessors.getFieldAccessor(HANDLE_TYPE, Map.class, true);
private static final ConstructorAccessor INT_ARRAY_LIST_CONSTRUCTOR =
Accessors.getConstructorAccessor(MinecraftReflection.getIntArrayListClass(), Collection.class);
private static final MethodAccessor CREATE_REGISTRY_KEY = Accessors.getMethodAccessor(
MinecraftReflection.getResourceKey(),
"createRegistryKey",
MinecraftReflection.getMinecraftKeyClass());
public static final EquivalentConverter<MinecraftKey> REGISTRY_KEY_CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(MinecraftKey specific) {
return CREATE_REGISTRY_KEY.invoke(null, MinecraftKey.getConverter().getGeneric(specific));
}
@Override
public MinecraftKey getSpecific(Object generic) {
return WrappedResourceKey.fromHandle(generic).getLocation();
}
@Override
public Class<MinecraftKey> getSpecificType() {
return MinecraftKey.class;
}
});
public static final EquivalentConverter<WrappedTagPayload> CONVERTER =
Converters.ignoreNull(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedTagPayload specific) {
Map<Object, Object> tags = new HashMap<>();
for (Map.Entry<MinecraftKey, List<Integer>> entry : specific.tags.entrySet()) {
tags.put(
MinecraftKey.getConverter().getGeneric(entry.getKey()),
INT_ARRAY_LIST_CONSTRUCTOR.invoke(entry.getValue()));
}
return CONSTRUCTOR.invoke(tags);
}
@Override
public WrappedTagPayload getSpecific(Object generic) {
Map<?, ?> genericTags = (Map<?, ?>) TAGS.get(generic);
Map<MinecraftKey, List<Integer>> tags = new HashMap<>();
for (Map.Entry<?, ?> entry : genericTags.entrySet()) {
List<Integer> ids = new ArrayList<>();
for (Object id : (Iterable<?>) entry.getValue()) {
ids.add((Integer) id);
}
tags.put(MinecraftKey.getConverter().getSpecific(entry.getKey()), ids);
}
return new WrappedTagPayload(tags);
}
@Override
public Class<WrappedTagPayload> getSpecificType() {
return WrappedTagPayload.class;
}
});
private final Map<MinecraftKey, List<Integer>> tags;
public WrappedTagPayload(Map<MinecraftKey, List<Integer>> tags) {
this.tags = copy(tags);
}
public Map<MinecraftKey, List<Integer>> getTags() {
return copy(tags);
}
private static Map<MinecraftKey, List<Integer>> copy(Map<MinecraftKey, List<Integer>> tags) {
Map<MinecraftKey, List<Integer>> copy = new HashMap<>();
for (Map.Entry<MinecraftKey, List<Integer>> entry : tags.entrySet()) {
copy.put(entry.getKey(), List.copyOf(entry.getValue()));
}
return copy;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedTagPayload that)) {
return false;
}
return Objects.equals(tags, that.tags);
}
@Override
public int hashCode() {
return Objects.hash(tags);
}
@Override
public String toString() {
return "WrappedTagPayload{tags=" + tags + '}';
}
}
@@ -0,0 +1,237 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.base.Preconditions;
/**
* Wrapper around the NMS {@code VecDelta} type, introduced in Minecraft 26.3.
* <p>
* Used by {@code REL_ENTITY_MOVE} and {@code REL_ENTITY_MOVE_LOOK} to store the position change. A delta is either
* linear (a single offset) or stepped (several offsets reached at given ticks). Offsets use the same encoding as the
* short fields of older versions.
* <pre>{@code
* packet.getVecDeltas().write(0, WrappedVecDelta.linear(deltaX, deltaY, deltaZ));
* }</pre>
*/
public final class WrappedVecDelta extends AbstractWrapper {
private static Class<?> STEPPED_CLASS;
private static ConstructorAccessor LINEAR_CONSTRUCTOR;
private static ConstructorAccessor STEPPED_CONSTRUCTOR;
private static ConstructorAccessor STEP_CONSTRUCTOR;
private static StructureModifier<Object> LINEAR_MODIFIER;
private static StructureModifier<Object> STEPPED_MODIFIER;
private static StructureModifier<Object> STEP_MODIFIER;
private static volatile boolean initialized;
private WrappedVecDelta(Object handle) {
super(getVecDeltaClass());
setHandle(handle);
}
private static Class<?> getVecDeltaClass() {
return MinecraftReflection.getVecDeltaClass()
.orElseThrow(() -> new UnsupportedOperationException("VecDelta requires Minecraft 26.3 or newer"));
}
private static void initialize() {
if (initialized) {
return;
}
Class<?> vecDeltaClass = getVecDeltaClass();
Class<?> linearClass = null;
Class<?> stepClass = null;
for (Class<?> nested : vecDeltaClass.getDeclaredClasses()) {
if (!vecDeltaClass.isAssignableFrom(nested)) {
continue;
}
Field steps = getListField(nested);
if (steps != null) {
STEPPED_CLASS = nested;
stepClass = (Class<?>) ((ParameterizedType) steps.getGenericType()).getActualTypeArguments()[0];
} else {
linearClass = nested;
}
}
Preconditions.checkState(linearClass != null && STEPPED_CLASS != null, "Unable to find VecDelta implementations");
LINEAR_CONSTRUCTOR = Accessors.getConstructorAccessor(linearClass, short.class, short.class, short.class);
STEPPED_CONSTRUCTOR = Accessors.getConstructorAccessor(STEPPED_CLASS, List.class);
STEP_CONSTRUCTOR = Accessors.getConstructorAccessor(stepClass, short.class, short.class, short.class, int.class);
LINEAR_MODIFIER = new StructureModifier<>(linearClass);
STEPPED_MODIFIER = new StructureModifier<>(STEPPED_CLASS);
STEP_MODIFIER = new StructureModifier<>(stepClass);
initialized = true;
}
private static Field getListField(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (List.class.equals(field.getType())) {
return field;
}
}
return null;
}
public static WrappedVecDelta fromHandle(Object handle) {
return new WrappedVecDelta(handle);
}
/**
* Creates a linear delta.
*
* @param deltaX - the encoded x offset.
* @param deltaY - the encoded y offset.
* @param deltaZ - the encoded z offset.
* @return the delta.
*/
public static WrappedVecDelta linear(short deltaX, short deltaY, short deltaZ) {
initialize();
return new WrappedVecDelta(LINEAR_CONSTRUCTOR.invoke(deltaX, deltaY, deltaZ));
}
/**
* Creates a stepped delta.
*
* @param steps - the steps in order, must not be empty.
* @return the delta.
*/
public static WrappedVecDelta stepped(List<Step> steps) {
Preconditions.checkArgument(steps != null && !steps.isEmpty(), "steps cannot be empty");
initialize();
List<Object> handles = new ArrayList<>(steps.size());
for (Step step : steps) {
handles.add(STEP_CONSTRUCTOR.invoke(step.getDeltaX(), step.getDeltaY(), step.getDeltaZ(), step.getTicks()));
}
return new WrappedVecDelta(STEPPED_CONSTRUCTOR.invoke(Collections.unmodifiableList(handles)));
}
public boolean isStepped() {
initialize();
return STEPPED_CLASS.isInstance(handle);
}
/**
* @return the encoded x offset of a linear delta.
* @throws IllegalStateException if the delta is stepped.
*/
public short getDeltaX() {
return readLinear(0);
}
/**
* @return the encoded y offset of a linear delta.
* @throws IllegalStateException if the delta is stepped.
*/
public short getDeltaY() {
return readLinear(1);
}
/**
* @return the encoded z offset of a linear delta.
* @throws IllegalStateException if the delta is stepped.
*/
public short getDeltaZ() {
return readLinear(2);
}
private short readLinear(int index) {
Preconditions.checkState(!isStepped(), "Stepped deltas have no single offset");
return LINEAR_MODIFIER.withTarget(handle).<Short>withType(short.class).read(index);
}
/**
* @return the steps of a stepped delta.
* @throws IllegalStateException if the delta is linear.
*/
public List<Step> getSteps() {
Preconditions.checkState(isStepped(), "Linear deltas have no steps");
List<?> handles = STEPPED_MODIFIER.withTarget(handle).<List<?>>withType(List.class).read(0);
List<Step> steps = new ArrayList<>(handles.size());
for (Object stepHandle : handles) {
StructureModifier<Object> step = STEP_MODIFIER.withTarget(stepHandle);
StructureModifier<Short> shorts = step.withType(short.class);
steps.add(new Step(shorts.read(0), shorts.read(1), shorts.read(2), step.<Integer>withType(int.class).read(0)));
}
return Collections.unmodifiableList(steps);
}
public static EquivalentConverter<WrappedVecDelta> getConverter() {
return Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle,
WrappedVecDelta::fromHandle, WrappedVecDelta.class));
}
public static final class Step {
private final short deltaX;
private final short deltaY;
private final short deltaZ;
private final int ticks;
public Step(short deltaX, short deltaY, short deltaZ, int ticks) {
this.deltaX = deltaX;
this.deltaY = deltaY;
this.deltaZ = deltaZ;
this.ticks = ticks;
}
public short getDeltaX() {
return deltaX;
}
public short getDeltaY() {
return deltaY;
}
public short getDeltaZ() {
return deltaZ;
}
public int getTicks() {
return ticks;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Step)) return false;
Step that = (Step) obj;
return deltaX == that.deltaX && deltaY == that.deltaY && deltaZ == that.deltaZ && ticks == that.ticks;
}
@Override
public int hashCode() {
return Objects.hash(deltaX, deltaY, deltaZ, ticks);
}
@Override
public String toString() {
return "Step[deltaX=" + deltaX + ", deltaY=" + deltaY + ", deltaZ=" + deltaZ + ", ticks=" + ticks + "]";
}
}
}
@@ -1,194 +0,0 @@
package com.comphenix.protocol.wrappers;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Wraps the NMS {@code WeightedList<E>} type, representing a list of weighted
* entries where each entry has a value and an integer weight.
*
* <p>The element type {@code T} is the <em>Bukkit / ProtocolLib</em> representation
* of the NMS element type, converted via the supplied {@link EquivalentConverter}.
*
* @param <T> the Bukkit-side element type
*/
public class WrappedWeightedList<T> {
// ── NMS reflection handles (eagerly resolved at class load) ──────────
private static final Class<?> WEIGHTED_LIST_CLASS;
private static final Class<?> WEIGHTED_CLASS;
private static final MethodAccessor WEIGHTED_LIST_OF; // static WeightedList.of(List<Weighted>)
private static final MethodAccessor WEIGHTED_LIST_UNWRAP; // WeightedList.unwrap() → List<Weighted>
private static final ConstructorAccessor WEIGHTED_CTOR; // new Weighted(Object value, int weight)
private static final MethodAccessor WEIGHTED_VALUE; // Weighted.value()
private static final MethodAccessor WEIGHTED_WEIGHT; // Weighted.weight()
static {
WEIGHTED_LIST_CLASS = MinecraftReflection.getMinecraftClass("util.random.WeightedList");
WEIGHTED_CLASS = MinecraftReflection.getMinecraftClass("util.random.Weighted");
WEIGHTED_LIST_OF = Accessors.getMethodAccessor(WEIGHTED_LIST_CLASS, "of", List.class);
WEIGHTED_LIST_UNWRAP = Accessors.getMethodAccessor(WEIGHTED_LIST_CLASS, "unwrap");
WEIGHTED_CTOR = Accessors.getConstructorAccessor(WEIGHTED_CLASS, Object.class, int.class);
WEIGHTED_VALUE = Accessors.getMethodAccessor(WEIGHTED_CLASS, "value");
WEIGHTED_WEIGHT = Accessors.getMethodAccessor(WEIGHTED_CLASS, "weight");
}
/**
* Returns the NMS {@code WeightedList} class.
*/
public static Class<?> getNmsClass() {
return WEIGHTED_LIST_CLASS;
}
// ── Instance data ────────────────────────────────────────────────────
private final List<Entry<T>> entries;
public WrappedWeightedList() {
this.entries = new ArrayList<>();
}
public WrappedWeightedList(List<Entry<T>> entries) {
this.entries = new ArrayList<>(entries);
}
/**
* Returns a mutable view of the entries in this weighted list. Mutations are
* reflected on this wrapper.
*/
public List<Entry<T>> getEntries() {
return entries;
}
/**
* Replaces all entries with the given list. The argument is copied; subsequent
* modifications to it are not reflected on this wrapper.
*/
public void setEntries(List<Entry<T>> entries) {
this.entries.clear();
this.entries.addAll(entries);
}
/**
* Appends a single entry.
*/
public void addEntry(Entry<T> entry) {
this.entries.add(entry);
}
/**
* Appends a single value/weight entry.
*/
public void addEntry(T value, int weight) {
this.entries.add(new Entry<>(value, weight));
}
// ── Entry record ─────────────────────────────────────────────────────
/**
* A single weighted entry pairing a value with an integer weight.
*
* @param <T> the Bukkit-side element type
*/
public record Entry<T>(T value, int weight) {
/** Backwards-compatible accessor for callers that used {@code getValue()}. */
public T getValue() {
return value;
}
/** Backwards-compatible accessor for callers that used {@code getWeight()}. */
public int getWeight() {
return weight;
}
}
// ── Converter factory ────────────────────────────────────────────────
/**
* Creates an {@link EquivalentConverter} that converts between
* {@code WrappedWeightedList<T>} and the NMS {@code WeightedList<E>}.
*
* @param elementConverter converter between the Bukkit type {@code T}
* and the NMS element type {@code E}
* @param <T> Bukkit-side element type
* @return the converter
*/
public static <T> EquivalentConverter<WrappedWeightedList<T>> getConverter(
EquivalentConverter<T> elementConverter) {
return new EquivalentConverter<>() {
@Override
@SuppressWarnings("unchecked")
public WrappedWeightedList<T> getSpecific(Object generic) {
List<Object> nmsWeightedEntries = (List<Object>) WEIGHTED_LIST_UNWRAP.invoke(generic);
List<Entry<T>> entries = new ArrayList<>(nmsWeightedEntries.size());
for (Object nmsWeighted : nmsWeightedEntries) {
Object nmsValue = WEIGHTED_VALUE.invoke(nmsWeighted);
int weight = (int) WEIGHTED_WEIGHT.invoke(nmsWeighted);
T value = elementConverter.getSpecific(nmsValue);
entries.add(new Entry<>(value, weight));
}
return new WrappedWeightedList<>(entries);
}
@Override
public Object getGeneric(WrappedWeightedList<T> specific) {
List<Object> nmsWeightedEntries = new ArrayList<>(specific.entries.size());
for (Entry<T> entry : specific.entries) {
Object nmsValue = elementConverter.getGeneric(entry.getValue());
Object nmsWeighted = WEIGHTED_CTOR.invoke(nmsValue, entry.getWeight());
nmsWeightedEntries.add(nmsWeighted);
}
return WEIGHTED_LIST_OF.invoke(null, nmsWeightedEntries);
}
@Override
@SuppressWarnings("unchecked")
public Class<WrappedWeightedList<T>> getSpecificType() {
return (Class<WrappedWeightedList<T>>) (Class<?>) WrappedWeightedList.class;
}
};
}
// ── Convenience factories ────────────────────────────────────────────
/**
* Creates an empty weighted list.
*/
public static <T> WrappedWeightedList<T> empty() {
return new WrappedWeightedList<>(Collections.emptyList());
}
// ── Object methods ───────────────────────────────────────────────────
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof WrappedWeightedList<?> that)) return false;
return Objects.equals(entries, that.entries);
}
@Override
public int hashCode() {
return Objects.hashCode(entries);
}
@Override
public String toString() {
return "WrappedWeightedList{entries=" + entries + "}";
}
}
@@ -1,69 +0,0 @@
package net.dmulloy2.protocol;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketContainer;
import java.util.Objects;
import org.bukkit.entity.Player;
/**
* Base class for all packet wrappers. Provides access to the underlying
* {@link PacketContainer} and convenience methods for sending/receiving.
*/
public abstract class AbstractPacket {
protected final PacketContainer handle;
protected AbstractPacket(PacketContainer handle, PacketType type) {
if (handle == null) {
throw new IllegalArgumentException("Packet handle cannot be null");
}
if (type == null) {
throw new IllegalArgumentException("Packet type cannot be null");
}
if (!handle.getType().equals(type)) {
throw new IllegalArgumentException(
"Invalid packet type: expected " + type + ", got " + handle.getType());
}
this.handle = handle;
}
/**
* Returns the underlying {@link PacketContainer}.
*/
public PacketContainer getHandle() {
return handle;
}
/**
* Sends this packet to a player (server → client).
*/
public void sendPacket(Player receiver) {
ProtocolLibrary.getProtocolManager().sendServerPacket(receiver, getHandle());
}
/**
* Simulates receiving this packet from a player (client → server).
*/
public void receivePacket(Player player) {
ProtocolLibrary.getProtocolManager().receiveClientPacket(player, getHandle());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractPacket other = (AbstractPacket) o;
return Objects.equals(handle, other.handle);
}
@Override
public int hashCode() {
return Objects.hashCode(handle);
}
@Override
public String toString() {
return getClass().getSimpleName() + "{handle=" + handle + "}";
}
}
@@ -1,36 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCodeOfConductPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundCodeOfConductPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.CODE_OF_CONDUCT;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(String.class);
public WrappedClientboundCodeOfConductPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCodeOfConductPacket(String codeOfConduct) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(codeOfConduct)));
}
public WrappedClientboundCodeOfConductPacket(PacketContainer packet) {
super(packet, TYPE);
}
public String getCodeOfConduct() {
return handle.getStrings().read(0);
}
public void setCodeOfConduct(String codeOfConduct) {
handle.getStrings().write(0, codeOfConduct);
}
}
@@ -1,43 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.MinecraftKey;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCookieRequestPacket} (configuration phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Identifier key} – namespaced key identifying the cookie to request</li>
* </ul>
*/
public class WrappedClientboundCookieRequestPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.COOKIE_REQUEST;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getMinecraftKeyClass(), MinecraftKey.getConverter());
public WrappedClientboundCookieRequestPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCookieRequestPacket(MinecraftKey key) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(key)));
}
public WrappedClientboundCookieRequestPacket(PacketContainer packet) {
super(packet, TYPE);
}
public MinecraftKey getKey() {
return handle.getMinecraftKeys().read(0);
}
public void setKey(MinecraftKey key) {
handle.getMinecraftKeys().write(0, key);
}
}
@@ -1,42 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCustomPayloadPacket} (configuration phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code CustomPacketPayload payload} – channel-specific payload</li>
* </ul>
*/
public class WrappedClientboundCustomPayloadPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.CUSTOM_PAYLOAD;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(CustomPacketPayloadWrapper.getCustomPacketPayloadClass(), CustomPacketPayloadWrapper.getConverter());
public WrappedClientboundCustomPayloadPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCustomPayloadPacket(CustomPacketPayloadWrapper payload) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(payload)));
}
public WrappedClientboundCustomPayloadPacket(PacketContainer packet) {
super(packet, TYPE);
}
public CustomPacketPayloadWrapper getPayload() {
return handle.getCustomPacketPayloads().read(0);
}
public void setPayload(CustomPacketPayloadWrapper payload) {
handle.getCustomPacketPayloads().write(0, payload);
}
}
@@ -1,48 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.Converters;
import java.util.Map;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCustomReportDetailsPacket} (configuration phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Map<String, String> details} – custom crash report detail entries</li>
* </ul>
*/
public class WrappedClientboundCustomReportDetailsPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.REPORT_DETAILS;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(Map.class);
public WrappedClientboundCustomReportDetailsPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCustomReportDetailsPacket(Map<String, String> details) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(details)));
}
public WrappedClientboundCustomReportDetailsPacket(PacketContainer packet) {
super(packet, TYPE);
}
public Map<String, String> getDetails() {
return handle.getMaps(
Converters.passthrough(String.class),
Converters.passthrough(String.class)).readSafely(0);
}
public void setDetails(Map<String, String> details) {
handle.getMaps(
Converters.passthrough(String.class),
Converters.passthrough(String.class)).writeSafely(0, details);
}
}
@@ -1,39 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundDisconnectPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundDisconnectPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.DISCONNECT;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
public WrappedClientboundDisconnectPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDisconnectPacket(WrappedChatComponent reason) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(reason)));
}
public WrappedClientboundDisconnectPacket(PacketContainer packet) {
super(packet, TYPE);
}
public WrappedChatComponent getReason() {
return handle.getChatComponents().read(0);
}
public void setReason(WrappedChatComponent reason) {
handle.getChatComponents().write(0, reason);
}
}
@@ -1,36 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundKeepAlivePacket} (configuration phase, clientbound).
*/
public class WrappedClientboundKeepAlivePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.KEEP_ALIVE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(long.class);
public WrappedClientboundKeepAlivePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundKeepAlivePacket(long id) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id)));
}
public WrappedClientboundKeepAlivePacket(PacketContainer packet) {
super(packet, TYPE);
}
public long getId() {
return handle.getLongs().read(0);
}
public void setId(long id) {
handle.getLongs().write(0, id);
}
}
@@ -1,36 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundPingPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundPingPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.PING;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class);
public WrappedClientboundPingPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundPingPacket(int id) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id)));
}
public WrappedClientboundPingPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getId() {
return handle.getIntegers().read(0);
}
public void setId(int id) {
handle.getIntegers().write(0, id);
}
}
@@ -1,27 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundRegistryDataPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundRegistryDataPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.REGISTRY_DATA;
public WrappedClientboundRegistryDataPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundRegistryDataPacket(PacketContainer packet) {
super(packet, TYPE);
}
// TODO: missing field 'registry' (NMS type: ResourceKey<? extends Registry<?>>)
// Use getMinecraftKeys().read(0) for the registry key identifier, or getSpecificModifier(ResourceKey.class).read(0).
// TODO: missing field 'entries' (NMS type: List<RegistrySynchronization.PackedRegistryEntry>)
// Each entry has an id (Identifier) and an optional NBT tag. Use handle.getModifier().read(1)
// for the raw List, or add a dedicated WrappedPackedRegistryEntry class.
}
@@ -1,45 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.Converters;
import java.util.Optional;
import java.util.UUID;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundResourcePackPopPacket} (configuration phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Optional<UUID> id} – UUID of the resource pack to remove, or empty to remove all</li>
* </ul>
*/
public class WrappedClientboundResourcePackPopPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.REMOVE_RESOURCE_PACK;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(Optional.class);
public WrappedClientboundResourcePackPopPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundResourcePackPopPacket(Optional<UUID> id) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id)));
}
public WrappedClientboundResourcePackPopPacket(PacketContainer packet) {
super(packet, TYPE);
}
public Optional<UUID> getId() {
return handle.getOptionals(Converters.passthrough(UUID.class)).readSafely(0);
}
public void setId(Optional<UUID> id) {
handle.getOptionals(Converters.passthrough(UUID.class)).writeSafely(0, id);
}
}
@@ -1,79 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import java.util.Optional;
import java.util.UUID;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundResourcePackPushPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundResourcePackPushPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.ADD_RESOURCE_PACK;
// NMS constructor order: (UUID id, String url, String hash, boolean required, Optional<Component> prompt)
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(UUID.class)
.withParam(String.class)
.withParam(String.class)
.withParam(boolean.class)
.withParam(Optional.class, Converters.optional(BukkitConverters.getWrappedChatComponentConverter()));
public WrappedClientboundResourcePackPushPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundResourcePackPushPacket(String url, String hash, boolean required, UUID id, Optional<WrappedChatComponent> prompt) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id, url, hash, required, prompt)));
}
public WrappedClientboundResourcePackPushPacket(PacketContainer packet) {
super(packet, TYPE);
}
public String getUrl() {
return handle.getStrings().read(0);
}
public void setUrl(String url) {
handle.getStrings().write(0, url);
}
public String getHash() {
return handle.getStrings().read(1);
}
public void setHash(String hash) {
handle.getStrings().write(1, hash);
}
public boolean isRequired() {
return handle.getBooleans().read(0);
}
public void setRequired(boolean required) {
handle.getBooleans().write(0, required);
}
public UUID getId() {
return handle.getUUIDs().read(0);
}
public void setId(UUID id) {
handle.getUUIDs().write(0, id);
}
public Optional<WrappedChatComponent> getPrompt() {
return handle.getOptionals(BukkitConverters.getWrappedChatComponentConverter()).read(0);
}
public void setPrompt(Optional<WrappedChatComponent> prompt) {
handle.getOptionals(BukkitConverters.getWrappedChatComponentConverter()).write(0, prompt);
}
}
@@ -1,42 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedKnownPack;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.List;
/**
* Wrapper for {@code ClientboundSelectKnownPacks} (configuration phase, clientbound).
* Selects known packs.
*/
public class WrappedClientboundSelectKnownPacksPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.SELECT_KNOWN_PACKS;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(List.class, BukkitConverters.getListConverter(WrappedKnownPack.CONVERTER));
public WrappedClientboundSelectKnownPacksPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundSelectKnownPacksPacket(List<WrappedKnownPack> knownPacks) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(knownPacks)));
}
public WrappedClientboundSelectKnownPacksPacket(PacketContainer packet) {
super(packet, TYPE);
}
public List<WrappedKnownPack> getKnownPacks() {
return handle.getLists(WrappedKnownPack.CONVERTER).read(0);
}
public void setKnownPacks(List<WrappedKnownPack> knownPacks) {
handle.getLists(WrappedKnownPack.CONVERTER).write(0, knownPacks);
}
}
@@ -1,84 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AutoWrapper;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Either;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.List;
/**
* Wrapper for {@code ClientboundServerLinksPacket} (configuration phase, clientbound).
*
* <p>Fields:
* <ul>
* <li>{@code List<ServerLinks.UntrustedEntry> links} – server-provided links</li>
* </ul>
*
* <p>NMS: {@code record UntrustedEntry(Either<KnownLinkType, Component> type, String link)}
*/
public class WrappedClientboundServerLinksPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.SERVER_LINKS;
/** Mirrors NMS {@code ServerLinks.KnownLinkType}; constant order must match. */
public enum KnownLinkType {
BUG_REPORT, COMMUNITY_GUIDELINES, SUPPORT, STATUS, FEEDBACK,
COMMUNITY, WEBSITE, FORUMS, NEWS, ANNOUNCEMENTS
}
private static final Class<?> NMS_KNOWN_LINK_TYPE_CLASS =
MinecraftReflection.getMinecraftClass("server.ServerLinks$KnownLinkType");
private static final EquivalentConverter<Either<KnownLinkType, WrappedChatComponent>> TYPE_CONVERTER =
BukkitConverters.getEitherConverter(
EnumWrappers.getGenericConverter(NMS_KNOWN_LINK_TYPE_CLASS, KnownLinkType.class),
BukkitConverters.getWrappedChatComponentConverter());
private static final AutoWrapper<WrappedUntrustedEntry> ENTRY_WRAPPER =
AutoWrapper.wrap(WrappedUntrustedEntry.class, "server.ServerLinks$UntrustedEntry")
.field(0, TYPE_CONVERTER);
public WrappedClientboundServerLinksPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundServerLinksPacket(PacketContainer packet) {
super(packet, TYPE);
}
/**
* Returns the list of server links.
*/
public List<WrappedUntrustedEntry> getLinks() {
List<WrappedUntrustedEntry> links = handle.getLists(ENTRY_WRAPPER).read(0);
return links != null ? links : List.of();
}
public void setLinks(List<WrappedUntrustedEntry> links) {
handle.getLists(ENTRY_WRAPPER).write(0, links);
}
/**
* POJO mirroring {@code record ServerLinks.UntrustedEntry(Either<KnownLinkType, Component> type, String link)}.
*/
public static final class WrappedUntrustedEntry {
/** Either a {@link KnownLinkType} (left) or a custom {@link WrappedChatComponent} display name (right). */
public Either<KnownLinkType, WrappedChatComponent> type;
/** The URL string. */
public String link;
public WrappedUntrustedEntry() {}
public WrappedUntrustedEntry(Either<KnownLinkType, WrappedChatComponent> type, String link) {
this.type = type;
this.link = link;
}
}
}
@@ -1,25 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundShowDialogPacket} (configuration phase, clientbound).
* Sends a dialog to the client.
*/
public class WrappedClientboundShowDialogPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.SHOW_DIALOG;
public WrappedClientboundShowDialogPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundShowDialogPacket(PacketContainer packet) {
super(packet, TYPE);
}
// TODO: missing field 'dialog' (NMS type: Holder<Dialog> — registry-backed dialog definition)
// Same as the game-phase ShowDialog packet. Use handle.getModifier().read(0) for the raw Holder<Dialog>.
}
@@ -1,53 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.MinecraftKey;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundStoreCookiePacket} (configuration phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Identifier key} – namespaced key identifying the cookie</li>
* <li>{@code byte[] payload} – cookie data (max 5120 bytes)</li>
* </ul>
*/
public class WrappedClientboundStoreCookiePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.STORE_COOKIE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getMinecraftKeyClass(), MinecraftKey.getConverter())
.withParam(byte[].class);
public WrappedClientboundStoreCookiePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundStoreCookiePacket(MinecraftKey key, byte[] payload) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(key, payload)));
}
public WrappedClientboundStoreCookiePacket(PacketContainer packet) {
super(packet, TYPE);
}
public MinecraftKey getKey() {
return handle.getMinecraftKeys().read(0);
}
public void setKey(MinecraftKey key) {
handle.getMinecraftKeys().write(0, key);
}
public byte[] getPayload() {
return handle.getByteArrays().read(0);
}
public void setPayload(byte[] payload) {
handle.getByteArrays().write(0, payload);
}
}
@@ -1,45 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundTransferPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundTransferPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.TRANSFER;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(String.class)
.withParam(int.class);
public WrappedClientboundTransferPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundTransferPacket(String host, int port) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(host, port)));
}
public WrappedClientboundTransferPacket(PacketContainer packet) {
super(packet, TYPE);
}
public String getHost() {
return handle.getStrings().read(0);
}
public void setHost(String host) {
handle.getStrings().write(0, host);
}
public int getPort() {
return handle.getIntegers().read(0);
}
public void setPort(int port) {
handle.getIntegers().write(0, port);
}
}
@@ -1,45 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.MinecraftKey;
import java.util.Set;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundUpdateEnabledFeaturesPacket} (configuration phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Set<ResourceLocation> features} – set of enabled feature flag identifiers</li>
* </ul>
*/
public class WrappedClientboundUpdateEnabledFeaturesPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.UPDATE_ENABLED_FEATURES;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(Set.class, BukkitConverters.getSetConverter(MinecraftKey.getConverter()));
public WrappedClientboundUpdateEnabledFeaturesPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundUpdateEnabledFeaturesPacket(Set<MinecraftKey> features) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(features)));
}
public WrappedClientboundUpdateEnabledFeaturesPacket(PacketContainer packet) {
super(packet, TYPE);
}
public Set<MinecraftKey> getFeatures() {
return handle.getSets(MinecraftKey.getConverter()).readSafely(0);
}
public void setFeatures(Set<MinecraftKey> features) {
handle.getSets(MinecraftKey.getConverter()).writeSafely(0, features);
}
}
@@ -1,44 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.WrappedTagPayload;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.Map;
/**
* Wrapper for {@code ClientboundUpdateTagsPacket} (configuration phase, clientbound).
*/
public class WrappedClientboundUpdateTagsPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Server.UPDATE_TAGS;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(Map.class, BukkitConverters.getMapConverter(
WrappedTagPayload.REGISTRY_KEY_CONVERTER,
WrappedTagPayload.CONVERTER));
public WrappedClientboundUpdateTagsPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundUpdateTagsPacket(Map<MinecraftKey, WrappedTagPayload> tags) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(tags)));
}
public WrappedClientboundUpdateTagsPacket(PacketContainer packet) {
super(packet, TYPE);
}
public Map<MinecraftKey, WrappedTagPayload> getTags() {
return handle.getMaps(WrappedTagPayload.REGISTRY_KEY_CONVERTER, WrappedTagPayload.CONVERTER).read(0);
}
public void setTags(Map<MinecraftKey, WrappedTagPayload> tags) {
handle.getMaps(WrappedTagPayload.REGISTRY_KEY_CONVERTER, WrappedTagPayload.CONVERTER).write(0, tags);
}
}
@@ -1,81 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.AutoWrapper;
import com.comphenix.protocol.wrappers.EnumWrappers;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ServerboundClientInformationPacket} (configuration phase, serverbound).
*
* <p>The packet wraps a single {@code ClientInformation} record, exposed via
* {@link #getInformation()} / {@link #setInformation(WrappedClientInformation)}.
*
* <p>NMS: {@code record ClientInformation(String language, int viewDistance,
* ChatVisiblity chatVisibility, boolean chatColors, int modelCustomisation,
* HumanoidArm mainHand, boolean textFilteringEnabled, boolean allowsListing,
* ParticleStatus particleStatus)}
*/
public class WrappedServerboundClientInformationPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.CLIENT_INFORMATION;
private static final Class<?> CLIENT_INFORMATION_CLASS =
MinecraftReflection.getMinecraftClass("server.level.ClientInformation");
private static final Class<?> HUMANOID_ARM_CLASS =
MinecraftReflection.getMinecraftClass("world.entity.HumanoidArm");
private static final Class<?> PARTICLE_STATUS_CLASS =
MinecraftReflection.getMinecraftClass("server.level.ParticleStatus");
private static final EnumWrappers.EnumConverter<MainHand> MAIN_HAND_CONVERTER =
new EnumWrappers.EnumConverter<>(HUMANOID_ARM_CLASS, MainHand.class);
private static final AutoWrapper<WrappedClientInformation> INFORMATION_WRAPPER =
AutoWrapper.wrap(WrappedClientInformation.class, CLIENT_INFORMATION_CLASS)
.field(2, EnumWrappers.getChatVisibilityConverter())
.field(5, MAIN_HAND_CONVERTER)
.field(8, EnumWrappers.getGenericConverter(PARTICLE_STATUS_CLASS, ParticleStatus.class));
public WrappedServerboundClientInformationPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundClientInformationPacket(PacketContainer packet) {
super(packet, TYPE);
}
/** Returns the client settings, read from the underlying NMS {@code ClientInformation} record. */
public WrappedClientInformation getInformation() {
return handle.getModifier().withType(CLIENT_INFORMATION_CLASS, INFORMATION_WRAPPER).read(0);
}
/** Writes the client settings back to the underlying NMS {@code ClientInformation} record. */
public void setInformation(WrappedClientInformation info) {
handle.getModifier().withType(CLIENT_INFORMATION_CLASS, INFORMATION_WRAPPER).write(0, info);
}
/** POJO mirroring {@code record ClientInformation(...)}. */
public static final class WrappedClientInformation {
public String language;
public int viewDistance;
/** ProtocolLib {@link EnumWrappers.ChatVisibility}. */
public EnumWrappers.ChatVisibility chatVisibility;
public boolean chatColors;
public int modelCustomisation;
/** Dominant arm: {@link MainHand#LEFT} or {@link MainHand#RIGHT}. */
public MainHand mainHand;
public boolean textFilteringEnabled;
public boolean allowsListing;
public ParticleStatus particleStatus;
public WrappedClientInformation() {}
}
/** Mirrors {@code HumanoidArm}: the player's dominant arm setting. */
public enum MainHand { LEFT, RIGHT }
/** Mirrors NMS {@code ParticleStatus}; constant order must match. */
public enum ParticleStatus { ALL, DECREASED, MINIMAL }
}
@@ -1,54 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.MinecraftKey;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ServerboundCookieResponsePacket} (configuration phase, serverbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Identifier key} – namespaced key identifying the cookie</li>
* <li>{@code byte[] payload} – cookie bytes, or {@code null} if the client has no value</li>
* </ul>
*/
public class WrappedServerboundCookieResponsePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.COOKIE_RESPONSE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getMinecraftKeyClass(), MinecraftKey.getConverter())
.withParam(byte[].class);
public WrappedServerboundCookieResponsePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundCookieResponsePacket(MinecraftKey key, byte[] payload) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(key, payload)));
}
public WrappedServerboundCookieResponsePacket(PacketContainer packet) {
super(packet, TYPE);
}
public MinecraftKey getKey() {
return handle.getMinecraftKeys().readSafely(0);
}
public void setKey(MinecraftKey key) {
handle.getMinecraftKeys().writeSafely(0, key);
}
public byte[] getPayload() {
return handle.getByteArrays().readSafely(0);
}
public void setPayload(byte[] payload) {
handle.getByteArrays().writeSafely(0, payload);
}
}
@@ -1,45 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.Optional;
/**
* Wrapper for {@code ServerboundCustomClickActionPacket} (configuration phase, serverbound).
*
* <p>Sent when the client clicks a custom button identified by a resource-location key.
*/
public class WrappedServerboundCustomClickActionPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.CUSTOM_CLICK_ACTION;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getMinecraftKeyClass(), MinecraftKey.getConverter())
.withParam(Optional.class, Converters.optional(BukkitConverters.getNbtConverter()));
public WrappedServerboundCustomClickActionPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundCustomClickActionPacket(MinecraftKey id, Optional<NbtBase<?>> payload) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id, payload)));
}
public WrappedServerboundCustomClickActionPacket(PacketContainer packet) {
super(packet, TYPE);
}
public MinecraftKey getId() {
return handle.getMinecraftKeys().read(0);
}
public void setId(MinecraftKey id) {
handle.getMinecraftKeys().write(0, id);
}
}
@@ -1,42 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ServerboundCustomPayloadPacket} (configuration phase, serverbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code CustomPacketPayload payload} – channel-specific payload data</li>
* </ul>
*/
public class WrappedServerboundCustomPayloadPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.CUSTOM_PAYLOAD;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(CustomPacketPayloadWrapper.getCustomPacketPayloadClass(), CustomPacketPayloadWrapper.getConverter());
public WrappedServerboundCustomPayloadPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundCustomPayloadPacket(CustomPacketPayloadWrapper payload) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(payload)));
}
public WrappedServerboundCustomPayloadPacket(PacketContainer packet) {
super(packet, TYPE);
}
public CustomPacketPayloadWrapper getPayload() {
return handle.getCustomPacketPayloads().read(0);
}
public void setPayload(CustomPacketPayloadWrapper payload) {
handle.getCustomPacketPayloads().write(0, payload);
}
}
@@ -1,36 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ServerboundKeepAlivePacket} (configuration phase, serverbound).
*/
public class WrappedServerboundKeepAlivePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.KEEP_ALIVE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(long.class);
public WrappedServerboundKeepAlivePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundKeepAlivePacket(long id) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id)));
}
public WrappedServerboundKeepAlivePacket(PacketContainer packet) {
super(packet, TYPE);
}
public long getId() {
return handle.getLongs().read(0);
}
public void setId(long id) {
handle.getLongs().write(0, id);
}
}
@@ -1,36 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ServerboundPongPacket} (configuration phase, serverbound).
*/
public class WrappedServerboundPongPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.PONG;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class);
public WrappedServerboundPongPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundPongPacket(int id) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id)));
}
public WrappedServerboundPongPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getId() {
return handle.getIntegers().read(0);
}
public void setId(int id) {
handle.getIntegers().write(0, id);
}
}
@@ -1,53 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.EnumWrappers;
import java.util.UUID;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ServerboundResourcePackPacket} (configuration phase, serverbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code UUID id} – unique identifier of the resource pack</li>
* <li>{@code ResourcePackStatusAction action} – the client's response status</li>
* </ul>
*/
public class WrappedServerboundResourcePackPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.RESOURCE_PACK_ACK;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(UUID.class)
.withParam(EnumWrappers.getResourcePackStatusClass(), EnumWrappers.getResourcePackStatusConverter());
public WrappedServerboundResourcePackPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundResourcePackPacket(UUID id, EnumWrappers.ResourcePackStatus action) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(id, action)));
}
public WrappedServerboundResourcePackPacket(PacketContainer packet) {
super(packet, TYPE);
}
public UUID getId() {
return handle.getUUIDs().read(0);
}
public void setId(UUID id) {
handle.getUUIDs().write(0, id);
}
public EnumWrappers.ResourcePackStatus getAction() {
return handle.getResourcePackStatus().read(0);
}
public void setAction(EnumWrappers.ResourcePackStatus action) {
handle.getResourcePackStatus().write(0, action);
}
}
@@ -1,42 +0,0 @@
package net.dmulloy2.protocol.wrappers.configuration.serverbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedKnownPack;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.List;
/**
* Wrapper for {@code ServerboundSelectKnownPacks} (configuration phase, serverbound).
* Selects known packs.
*/
public class WrappedServerboundSelectKnownPacksPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Configuration.Client.SELECT_KNOWN_PACKS;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(List.class, BukkitConverters.getListConverter(WrappedKnownPack.CONVERTER));
public WrappedServerboundSelectKnownPacksPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedServerboundSelectKnownPacksPacket(List<WrappedKnownPack> knownPacks) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(knownPacks)));
}
public WrappedServerboundSelectKnownPacksPacket(PacketContainer packet) {
super(packet, TYPE);
}
public List<WrappedKnownPack> getKnownPacks() {
return handle.getLists(WrappedKnownPack.CONVERTER).read(0);
}
public void setKnownPacks(List<WrappedKnownPack> knownPacks) {
handle.getLists(WrappedKnownPack.CONVERTER).write(0, knownPacks);
}
}
@@ -1,200 +0,0 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import java.util.UUID;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.util.Vector;
/**
* Wrapper for {@code ClientboundAddEntityPacket} (Play phase, clientbound).
*
* <p>The all-args constructor invokes the canonical NMS constructor through
* {@link EquivalentConstructor}, so values are populated by NMS exactly as if
* the server had constructed the packet itself.
*
* <p>The wrapper accepts pitch/yaw/head-yaw as <em>packed bytes</em> (the on-the-wire
* representation, 256 units = 360°) for parity with the field-level getters/setters;
* NMS internally stores them the same way and we round-trip through
* {@link #byteToAngle(byte)} on the way in. Use {@link #byteToAngle(byte)} /
* {@link #angleToByte(float)} when you need degrees.
*
* <p>Packet fields (NMS declaration order):
* <ul>
* <li>{@code int id} – entity ID</li>
* <li>{@code UUID uuid} – entity UUID</li>
* <li>{@code EntityType<?> type} – entity type</li>
* <li>{@code double x, y, z} – spawn position</li>
* <li>{@code Vec3 movement} – initial velocity</li>
* <li>{@code byte xRot} – pitch, packed (256 units = 360°)</li>
* <li>{@code byte yRot} – yaw, packed</li>
* <li>{@code byte yHeadRot} – head yaw, packed</li>
* <li>{@code int data} – optional type-specific data</li>
* </ul>
*/
public class WrappedClientboundAddEntityPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.SPAWN_ENTITY;
// NMS canonical ctor on Spigot 26.1:
// ClientboundAddEntityPacket(int id, UUID uuid, double x, double y, double z,
// float xRot, float yRot, EntityType<?> type, int data, Vec3 movement, double yHeadRot)
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(UUID.class)
.withParam(double.class)
.withParam(double.class)
.withParam(double.class)
.withParam(float.class)
.withParam(float.class)
.withParam(MinecraftReflection.getMinecraftClass("world.entity.EntityType"),
BukkitConverters.getEntityTypeConverter())
.withParam(int.class)
.withParam(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter())
.withParam(double.class);
public WrappedClientboundAddEntityPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundAddEntityPacket(int entityId, UUID entityUUID, EntityType entityType,
double x, double y, double z, Vector velocity,
byte pitchByte, byte yawByte, byte headYawByte, int data) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(
entityId, entityUUID, x, y, z,
byteToAngle(pitchByte), byteToAngle(yawByte),
entityType, data, velocity,
(double) byteToAngle(headYawByte))));
}
public WrappedClientboundAddEntityPacket(PacketContainer packet) {
super(packet, TYPE);
}
// -------------------------------------------------------------------------
// Entity ID
// -------------------------------------------------------------------------
public int getEntityId() {
return handle.getIntegers().read(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().write(0, entityId);
}
/** Resolves the live entity in the given world via entity ID. */
public Entity getEntity(World world) {
return handle.getEntityModifier(world).read(0);
}
/**
* Convenience setter that copies both the entity ID and UUID from a Bukkit entity.
* Does not set the entity type, position, velocity, or rotations — set those separately.
*/
public void setEntity(Entity entity) {
setEntityId(entity.getEntityId());
setEntityUUID(entity.getUniqueId());
}
// -------------------------------------------------------------------------
// UUID
// -------------------------------------------------------------------------
public UUID getEntityUUID() {
return handle.getUUIDs().read(0);
}
public void setEntityUUID(UUID uuid) {
handle.getUUIDs().write(0, uuid);
}
// -------------------------------------------------------------------------
// Entity type
// -------------------------------------------------------------------------
public EntityType getEntityType() {
return handle.getEntityTypeModifier().read(0);
}
public void setEntityType(EntityType type) {
handle.getEntityTypeModifier().write(0, type);
}
// -------------------------------------------------------------------------
// Position
// -------------------------------------------------------------------------
public double getX() { return handle.getDoubles().read(0); }
public void setX(double x) { handle.getDoubles().write(0, x); }
public double getY() { return handle.getDoubles().read(1); }
public void setY(double y) { handle.getDoubles().write(1, y); }
public double getZ() { return handle.getDoubles().read(2); }
public void setZ(double z) { handle.getDoubles().write(2, z); }
// -------------------------------------------------------------------------
// Initial velocity (Vec3)
// -------------------------------------------------------------------------
public Vector getVelocity() {
return handle.getVectors().read(0);
}
public void setVelocity(Vector velocity) {
handle.getVectors().write(0, velocity);
}
// -------------------------------------------------------------------------
// Packed rotation bytes (256 units = 360°)
// -------------------------------------------------------------------------
/** Pitch (xRot) as a packed byte. Convert with {@link #byteToAngle(byte)}. */
public byte getPitchByte() { return handle.getBytes().read(0); }
public void setPitchByte(byte xRot) { handle.getBytes().write(0, xRot); }
/** Yaw (yRot) as a packed byte. Convert with {@link #byteToAngle(byte)}. */
public byte getYawByte() { return handle.getBytes().read(1); }
public void setYawByte(byte yRot) { handle.getBytes().write(1, yRot); }
/** Head yaw (yHeadRot) as a packed byte. Convert with {@link #byteToAngle(byte)}. */
public byte getHeadYawByte() { return handle.getBytes().read(2); }
public void setHeadYawByte(byte yHeadRot) { handle.getBytes().write(2, yHeadRot); }
/**
* Converts a packed byte angle to degrees, matching NMS {@code Mth.unpackDegrees}.
* Treats {@code packed} as a signed byte: {@code (byte * 360) / 256.0f}.
*/
public static float byteToAngle(byte packed) {
return (packed * 360) / 256.0f;
}
/**
* Converts degrees to a packed byte angle, matching NMS {@code Mth.packDegrees}.
* Uses {@code Math.floor} (not int truncation) so negative angles round toward
* negative infinity, matching NMS exactly.
*/
public static byte angleToByte(float degrees) {
return (byte) (int) Math.floor(degrees * 256.0f / 360.0f);
}
// -------------------------------------------------------------------------
// Extra data
// -------------------------------------------------------------------------
/** Type-specific extra data (e.g. projectile type, falling-block state ID). */
public int getData() {
return handle.getIntegers().read(1);
}
public void setData(int data) {
handle.getIntegers().write(1, data);
}
}
Loaded 100 of 585 files, more files were not shown because too many files have changed in this diff. Show more