Compare commits

...
Author SHA1 Message Date
Dan Mulloy 79b09acb2a Restore compatibility with Java 17 2026-08-02 19:43:02 -05:00
Dan MulloyandCopilot 186f186e0a Update wrappers for current packet structures
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: bc4164a2-025e-45ca-9dac-4d5ee4213627
2026-08-02 19:38:37 -05:00
Dan Mulloy c4ae9122ce wip 2026-08-02 19:38:37 -05:00
Dan Mulloy 738cee2e6a fixes 2026-08-02 19:38:37 -05:00
Dan Mulloy d7431b9f1a more fixes 2026-08-02 19:38:37 -05:00
Dan Mulloy f30dbcfd53 entity packet helper methods/ctor 2026-08-02 19:38:37 -05:00
Dan Mulloy b2ef2de409 improve more wrappers, remove orphans 2026-08-02 19:38:37 -05:00
Dan Mulloy 1df4692038 more wrappers, more fixes 2026-08-02 19:38:37 -05:00
Dan Mulloy d2423c8ef7 work on all-args constructors, fix no-arg constructors 2026-08-02 19:38:36 -05:00
Dan Mulloy c42a5f1e26 fix test 2026-08-02 19:38:36 -05:00
Dan Mulloy 94860bbad0 more 2026-08-02 19:38:36 -05:00
Dan Mulloy 8c61001642 more 2026-08-02 19:38:36 -05:00
Dan Mulloy ee747010aa update more tests 2026-08-02 19:38:36 -05:00
Dan Mulloy 6250026956 rework tests 2026-08-02 19:38:36 -05:00
Dan Mulloy d91ec4b61a initial stab at having claude make wrappers 2026-08-02 19:38:36 -05:00
Dan Mulloy 98856a8b9f Restore compatibility with Java 17
fixes #3643
2026-08-02 19:35:14 -05:00
Dan Mulloy 5ec23ef33b Update guidelines for agents 2026-07-18 14:30:48 -05:00
Matvei Slotvinskiy 589ee12e12 perf: avoid per-call lambda allocation in CachedPackage#getPackageClass (#3651) 2026-07-18 14:04:58 +00:00
LoneDev 6cf7c7d8c3 Fixed typo (#2744) 2026-07-12 21:46:37 -05:00
gamerover98 3139e82aea Fixed TinyProtocol for 1.19+ (#2499) 2026-07-13 02:46:06 +00:00
yesidodo73 3385fe664b fix: select player info data list field (#3650) 2026-07-11 17:20:38 -05:00
Diogo Correia 0e267a5f1a fix: explicitly declare parameters of ServerPingRecord constructor (#3644)
Some Spigot forks (e.g., Leaf) add additional constructors with
different parameters, which causes the previous naive implementation of
getting the first available constructor to fail.
2026-06-24 10:14:39 -05:00
Dan Mulloy 2d3b3e3363 Update to Minecraft 26.2 (#3642) 2026-06-20 10:52:30 -05:00
Aldin 8eeb82f89d fix: only sign non snapshots and non jitpack versions (#3641) 2026-06-20 10:38:13 -05:00
Aldin b0b9b666a7 fix: only sign release versions to support jitpack snapshots builds again (#3639) 2026-06-19 23:05:20 -05:00
NekoEp1sode b723ff3351 fix: NettyChannelInjector.getPlayer() overwrites TemporaryPlayer with null (#3629) 2026-05-31 13:30:13 +00:00
Maurice Eisenblätter 72fd0709b6 Fix duplicate MONITOR listener invocation after async packet processing (#3630) 2026-05-31 09:26:34 -04:00
Richard Strunk 5a9afed462 Fix 26.1.2 problem with invalid constructor selection (#3626) 2026-05-12 14:17:40 -05:00
LeonTG 9404ce32db Fix Injector.getPlayer() using TemporaryPlayer when a Bukkit player is available (#3614) 2026-04-25 23:08:08 +00:00
Copilotanddmulloy2 6c6bde522e Add WrappedPositionMoveRotation for 1.21.2+ entity teleport packets (#3610)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dmulloy2 <3039119+dmulloy2@users.noreply.github.com>
2026-03-27 20:12:42 -05:00
Dan Mulloy f911ee904c Bump version to 5.5.0-SNAPSHOT 2026-03-26 20:42:11 -05:00
Dan Mulloy 22012d3dc9 Mark 26.1 as supported 2026-03-26 20:35:46 -05:00
Dan Mulloy c52b4bb10c Use Java's base64 encoder 2026-03-26 20:30:20 -05:00
Dan Mulloy b9f96469f5 Fix or ignore remaining tests 2026-03-26 20:22:18 -05:00
Dan Mulloy e5413ccc5b Update packet types and protocol versions 2026-03-26 19:52:09 -05:00
Dan Mulloy b37a3bcb13 Start update to 26.1 2026-03-24 22:21:54 -05:00
Dan Mulloy 19cb81b6ab Don't modify read-only bundle packets
Fixes #3603
2026-03-18 15:30:44 -04:00
coolomet f606cc954d Add support for ShreddedPaper's lazy packet execution (#3592) 2026-02-01 23:04:50 -06:00
Antti Koponen 774e679571 Mark 1.21.11 as supported (#3589) 2026-02-02 05:00:59 +00:00
icefear 1257506929 feat: add support for 1.21.11 (#3578) 2025-12-16 16:45:00 -06:00
Dan Mulloy 5773bcbc16 Fix WrappedServerPing#getPlayers in 1.21.10
Fixes #3570
2025-11-16 20:44:10 -06:00
Dan Mulloy 468082c9f9 Bump byte buddy version to fix Java 25
Fixes #3566
2025-10-12 19:28:46 -05:00
Dan Mulloy a11b403c4b Fix GameProfile properties in 1.21.9+
Fixes #3564
2025-10-12 19:26:19 -05:00
Dan Mulloy aa4fab1632 Update to 1.21.9 2025-10-05 18:20:39 -05:00
556 changed files with 36041 additions and 534 deletions

No files matched your search

+1
View File
@@ -1 +1,2 @@
* text=auto eol=lf
.github/workflows/*.lock.yml linguist-generated=true merge=ours
+70 -36
View File
@@ -1,38 +1,72 @@
Contributing to ProtocolLib
===========================
This page will detail specific things that must be done if you intend to contribute to this project.
# Contributing to ProtocolLib
## Updating ProtocolLib to a New Protocol Version
#### Before we get started:
1. **When do we need to update the protocol version?**
_A Minecraft protocol version is a number for each specification of the Minecraft protocol.
This means it is incremented when there is a change to the [Protocol Specification](http://wiki.vg/Protocol),
but not necessarily for each Minecraft version update. If the latest Minecraft version contains a protocol change,
that means ProtocolLib needs to be updated._
2. **When do we need to update other parts of ProtocolLib?**
_When the package version guard changes. The package version guard is the version string inserted
into the package identifier for CraftBukkit and net.minecraft.server (e.g. v1_9_R2). It is incremented
by Spigot if there is a major breaking change in the internal server code. This is to encourage plugins
that interact directly with CraftBukkit/nms code to update more specifically. However, since most of
ProtocolLib uses reflection, and the structure of packet classes is generally static, one will have
to go through the code and change imports from nms/CraftBukkit._
ProtocolLib supports a wide range of Minecraft versions through reflection and compatibility wrappers. Changes to packet
registration, reflection, or public wrappers can affect plugins on versions other than the one currently used for
development, so keep changes focused and preserve existing behavior unless a break is unavoidable.
#### Ready? Let's get started!
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [libera.chat](http://libera.chat)
or #spigot on [irc.spi.gt](http://irc.spi.gt) ([webchat](https://irc.spi.gt/iris/?channels=spigot)).
2. Search for usages of the now-defunct NMS package guard and change them.
3. The class `com.comphenix.protocol.PacketType` contains a list of all the packets. If any packets were added or removed
(or had their ID changed), make sure to update this list. If a packet was removed
in favor of usage of another packet, instead of removing it, move it to the bottom of the list
in its section, add a deprecation warning to it, and redirect it to the packet that replaced it.
4. `mvn` in the root directory to build the project.
5. If tests fail in the maven build, go through the tests to make sure you removed references to any removed packets
and changed the NMS version guards.
6. Increment the package version in `com.comphenix.protocol.utility.Constants`.
7. `com.comphenix.protocol.ProtocolLibrary` contains several constants that must be updated, including the Minecraft version
and the release date.
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
If the protocol version has been incremented, add a new line to the map.
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
the build fails, debug!
## 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.
```shell
./gradlew test
./gradlew build shadowJar
```
`shadowJar` produces `build/libs/ProtocolLib.jar` with the required Byte Buddy classes included.
ProtocolLib uses the Java 25 toolchain to compile against the current server, but production classes target Java 17 for
compatibility with older supported servers.
## Updating ProtocolLib for a Minecraft Release
Start by reviewing the [Java Edition protocol](https://minecraft.wiki/w/Java_Edition_protocol) and the corresponding
server classes. The server packet classes and codecs are authoritative when documentation and implementation differ.
### 1. Update version metadata
- Update `mcVersion` in `build.gradle.kts`.
- 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.
### 2. Reconcile packet types
`PacketType` must contain a matching definition for every packet in the current server protocol registries. Compare the
serverbound and clientbound packet classes and codecs against every protocol phase, including configuration.
- Add new, renamed, or split packets and update their current IDs and aliases.
- Do not remove an existing public `PacketType` constant. Move removed types to the deprecated section and document their
replacement when one exists.
- Represent split packets as separate packet types instead of redirecting incompatible packet shapes.
- Run the packet registry tests after changing packet definitions. A missing definition can prevent later packets in the
same protocol registry from being registered.
### 3. Update reflection and wrappers
- 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.
- 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.
- Keep converters symmetric and preserve wrapper cloning and equality semantics.
- A custom `StructureModifier` must maintain the complete modifier contract, including safe reads and writes, field count,
metadata, introspection, defaults, and `withTarget()`.
### 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.
- 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`.
- Before declaring a Minecraft release supported, build the shaded plugin and test it on the corresponding server.
## Pull Request Expectations
- Keep the change limited to the reported problem or release update.
- Include regression tests for packet, reflection, converter, or wrapper behavior changes.
- Preserve public API signatures and binary compatibility where possible.
- Clearly document any unavoidable compatibility change.
- Do not hide reflection failures with broad exception handling or silent fallbacks.
+123
View File
@@ -0,0 +1,123 @@
# ProtocolLib Agent Guide
This file applies to the entire repository. Read `.github/CONTRIBUTING.md` before changing Minecraft support, packet
definitions, reflection, or public wrappers.
## Repository Basics
- The main project uses the Gradle wrapper. Do not use `TinyProtocol/pom.xml` to build ProtocolLib.
- The Java toolchain and current Spigot dependency are defined in `build.gradle.kts`.
- Keep the Java 25 build toolchain separate from the Java 17 production bytecode target.
- Production code is under `src/main/java`; JUnit tests and NMS test initialization are under `src/test/java`.
- Keep changes focused. Do not combine protocol fixes with unrelated cleanup or formatting.
- Preserve public API and binary compatibility unless the task explicitly requires a breaking change.
## Build and Test Commands
```shell
./gradlew test
./gradlew build shadowJar
```
Run the smallest relevant test while developing:
```shell
./gradlew test --tests fully.qualified.TestClass
./gradlew test --tests fully.qualified.TestClass.testMethod
```
After changing production Java code, run the targeted tests and then the complete test suite. Use `build shadowJar` when
the task affects packaging, dependencies, release support, or the final plugin artifact.
Do not introduce another build system, test runner, formatter, or lint tool unless the repository adopts it separately.
## Important Code Areas
- `PacketType.java`: public packet constants, current IDs, aliases, and deprecated packet definitions.
- `injector/packet/internal/ProtocolRegistry_1_20_5.java`: current packet discovery and codec registration.
- `utility/MinecraftVersion.java`: named Minecraft versions and the latest supported release.
- `utility/MinecraftProtocolVersion.java`: Minecraft release to protocol-number mappings.
- `ProtocolLibrary.java`: supported version bounds and latest release date.
- `utility/MinecraftReflection.java`: centralized NMS and CraftBukkit class discovery.
- `reflect/StructureModifier.java`: indexed packet field access and conversion contract.
- `wrappers/`: stable public representations of unstable NMS structures.
- `events/PacketContainer.java` and `events/AbstractStructure.java`: packet construction and typed field access.
- `src/test/java/com/comphenix/protocol/BukkitInitialization.java`: test bootstrap for CraftBukkit and NMS access.
## Minecraft Release Updates
Follow the complete checklist in `.github/CONTRIBUTING.md`. In particular:
- Update all version metadata together; do not bump only the Gradle dependency.
- Reconcile every serverbound and clientbound packet in every protocol phase with `PacketType`.
- Add packet types for newly split packets instead of forcing incompatible shapes through an older packet constant.
- Preserve removed public `PacketType` constants in the deprecated section.
- Update `MinecraftProtocolVersion` only when the protocol number actually changed.
Every packet exposed by the current NMS protocol registries must have a matching `PacketType`. An undefined or incorrectly
renamed packet can abort registration of later packets in the same registry.
## Version Compatibility
- Put version checks at the narrowest affected boundary.
- 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.
- If Minecraft removed a concept, return an unsupported or absent result consistent with existing APIs. Do not claim an
unrelated packet or class is the removed type.
- When Minecraft splits one packet into several packets, expose and use the new packet types explicitly.
- Preserve unrelated packet fields when adapting or rewriting one logical value.
The current test dependency exercises the latest supported server. Review older guarded paths carefully because they may
not execute in the local test run.
## Packet and Reflection Rules
- Treat the current server packet classes, record components, constructors, and codecs as authoritative.
- Prefer existing `MinecraftReflection`, `FuzzyReflection`, and accessor helpers over new direct reflection.
- Prefer structural and type-based matching where obfuscated names are unstable.
- Search for an existing class resolver, converter, or accessor before adding another one.
- Cache stable reflective lookups following nearby patterns; do not repeatedly scan classes on packet hot paths.
- Do not hide reflection failures with broad catches, silent defaults, or unrelated fallback types.
- Account for static fields, inherited fields, records, nested implementations, and duplicate field types when writing
fuzzy contracts.
## `StructureModifier` Contract
Typed accessors returned from packet APIs must behave like normal `StructureModifier` instances.
- `size()`, `getFields()`, `getField()`, target metadata, and field metadata must agree.
- `readSafely()` returns `null` for an absent index.
- `writeSafely()` ignores an absent index without throwing.
- `withTarget()` must preserve the modifier's behavior for the new target.
- `writeDefaults()` must remain safe.
- Converters must be applied symmetrically on reads and writes.
If a packet no longer contains a real backing field, prefer an empty modifier when compatible. If a virtual field is
required for API compatibility, initialize all modifier metadata and test its inherited methods.
## Wrapper and Converter Rules
- Wrapper factories, converters, getters, setters, and `deepClone()` must describe the same logical value.
- Preserve converter symmetry: converting specific to generic and back must not unexpectedly lose represented state.
- If a wrapper handle changes from a nested value to an entire packet, define logical `equals()` and `hashCode()` so
unrelated packet metadata does not affect equality.
- Clones that represent the same logical value should compare equal.
- Do not fabricate legacy states that the modern wire format cannot distinguish; document or surface the limitation.
- Record or immutable packet updates must retain entity IDs, flags, and other components not owned by the wrapper.
## Tests
- Call `BukkitInitialization.initializeAll()` before tests that resolve CraftBukkit or NMS classes.
- Prefer regression tests using the actual Spigot packet classes supplied by the test dependency.
- Test reads, writes, safe access, cloning, equality, and preservation of unrelated fields when changing wrappers.
- Test modifier metadata and inherited methods when introducing a custom `StructureModifier`.
- Re-enable disabled tests when fixing the incompatibility that caused them to be disabled.
- Do not weaken assertions or encode known incorrect behavior merely to make a new Minecraft version pass.
## Documentation and Review
- Update Javadocs when version support, nullability, or unsupported behavior changes.
- Update `.github/CONTRIBUTING.md` when the repository-wide build or release workflow changes.
- Keep this file operational and durable; do not add task-specific notes, temporary workarounds, or current PR details.
- Before finishing, inspect the complete diff for accidental API changes and unrelated edits.
+368
View File
@@ -0,0 +1,368 @@
# 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)));
}
}
```
@@ -64,7 +64,7 @@ public abstract class TinyProtocol {
// Packets we have to intercept
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getClass("{nms}.PacketLoginInStart", "net.minecraft.network.protocol.login.PacketLoginInStart");
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
private static final FieldAccessor<String> getPlayerName = new PlayerNameAccessor();
// Speedup channel lookup
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
@@ -492,6 +492,59 @@ public abstract class TinyProtocol {
}
}
/**
* Get the player name from the login start packet.
* This fixes the issue from 1.19 where the GameProfile field has been removed from this login packet.
*
* @author gamerover98
*/
private static class PlayerNameAccessor implements FieldAccessor<String> {
private FieldAccessor<String> getPlayerName;
private FieldAccessor<GameProfile> getGameProfile;
PlayerNameAccessor() {
try {
this.getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
} catch (IllegalArgumentException illegalArgumentException) {
// nothing to do.
}
try {
//HOT-FIX for 1.19+
this.getPlayerName = Reflection.getField(PACKET_LOGIN_IN_START, String.class, 0);
} catch (IllegalArgumentException illegalArgumentException) {
// nothing to do.
}
if (getGameProfile == null && getPlayerName == null) {
throw new UnsupportedOperationException("The current server version is not supported by TinyProtocol");
}
}
@Override
public String get(Object target) {
if (getPlayerName != null) {
String playerName = getPlayerName.get(target);
return playerName.substring(0, Math.min(16, playerName.length()));
}
return getGameProfile.get(target).getName();
}
@Override
public void set(Object target, Object value) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public boolean hasField(Object target) {
return getPlayerName != null
? getPlayerName.hasField(target)
: getGameProfile.hasField(target);
}
}
/**
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
*
@@ -533,8 +586,7 @@ public abstract class TinyProtocol {
private void handleLoginStart(Channel channel, Object packet) {
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
GameProfile profile = getGameProfile.get(packet);
channelLookup.put(profile.getName(), channel);
channelLookup.put(getPlayerName.get(packet), channel);
}
}
}
+28 -30
View File
@@ -1,21 +1,20 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import io.github.patrick.gradle.remapper.RemapTask
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
`java-library`
`maven-publish`
`signing`
id("com.gradleup.shadow") version "9.0.2"
id("io.github.patrick.remapper") version "1.4.2"
id("com.vanniktech.maven.publish") version "0.34.0"
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"
}
group = "net.dmulloy2"
description = "Provides access to the Minecraft protocol"
val mcVersion = "1.21.8"
val mcVersion = "26.2"
val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val isJitPack = System.getenv("JITPACK")?.equals("true", ignoreCase = true) ?: false
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
@@ -45,32 +44,28 @@ repositories {
}
dependencies {
implementation("net.bytebuddy:byte-buddy:1.17.5")
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("io.netty:netty-all:4.0.23.Final")
/*
* TODO(fix): once you update kyori:adventure-text-serializer-gson please uncomment the TODO in
* com.comphenix.protocol.wrappers.WrappedComponentStyleTest if the following issue got fixed:
* https://github.com/KyoriPowered/adventure/issues/1194
*/
compileOnly("net.kyori:adventure-text-serializer-gson:4.21.0")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT")//:remapped-mojang")
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:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
testImplementation("org.mockito:mockito-core:5.6.0")
testImplementation("io.netty:netty-common:4.1.97.Final")
testImplementation("io.netty:netty-transport:4.1.97.Final")
testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT:remapped-mojang")
testImplementation("net.kyori:adventure-text-serializer-gson:4.14.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.14.0")
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")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion.set(JavaLanguageVersion.of(25))
}
}
tasks {
@@ -104,7 +99,7 @@ tasks {
archiveFileName = "ProtocolLib.jar"
}
remap {
/*remap {
dependsOn("shadowJar")
inputTask.set(getByName<ShadowJar>("shadowJar"))
@@ -114,7 +109,7 @@ tasks {
assemble {
dependsOn("remap")
}
}*/
javadoc {
options.encoding = "UTF-8"
@@ -122,12 +117,15 @@ tasks {
compileJava {
options.encoding = "UTF-8"
options.release.set(17)
}
}
mavenPublishing {
publishToMavenCentral()
signAllPublications()
if (!isSnapshot && !isJitPack) {
signAllPublications()
}
coordinates("$group", project.name, "$version")
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.4.1-SNAPSHOT
version=5.5.0-SNAPSHOT
mavenCentralUsername=
mavenCentralPassword=
signing.keyId=
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+13 -12
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
@@ -133,114 +133,121 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x17, "CustomChatCompletions");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x18, "CustomPayload", "SPacketCustomPayload");
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x19, "DamageEvent", "ClientboundDamageEventPacket");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1B, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1C, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1D, "DisguisedChat");
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1E, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x1F, "EntityPositionSync");
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x20, "Explode", "Explosion", "SPacketExplosion");
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x22, "GameEvent", "GameStateChange", "SPacketChangeGameState");
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x23, "HorseScreenOpen", "OpenWindowHorse");
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x24, "HurtAnimation", "ClientboundHurtAnimationPacket");
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x25, "InitializeBorder");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x26, "KeepAlive", "SPacketKeepAlive");
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x27, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x28, "LevelEvent", "WorldEvent", "SPacketEffect");
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x29, "LevelParticles", "WorldParticles", "SPacketParticles");
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x2A, "LightUpdate");
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x2B, "Login", "SPacketJoinGame");
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x2C, "MapItemData", "Map", "SPacketMaps");
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2D, "MerchantOffers", "OpenWindowMerchant");
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
public static final PacketType MOVE_MINECART = new PacketType(PROTOCOL, SENDER, 0x30, "MoveMinecart");
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x31, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x32, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x33, "OpenBook");
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x34, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x35, "OpenSignEditor", "SPacketSignEditorOpen");
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x36, "Ping");
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x37, "PongResponse");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x38, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerChat", "Chat", "SPacketChat");
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerCombatEnd");
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerCombatEnter");
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3D, "PlayerCombatKill");
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3E, "PlayerInfoRemove");
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3F, "PlayerInfoUpdate", "PlayerInfo");
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x41, "PlayerPosition", "Position");
public static final PacketType PLAYER_ROTATION = new PacketType(PROTOCOL, SENDER, 0x42, "PlayerRotation");
public static final PacketType RECIPE_BOOK_ADD = new PacketType(PROTOCOL, SENDER, 0x43, "RecipeBookAdd");
public static final PacketType RECIPE_BOOK_REMOVE = new PacketType(PROTOCOL, SENDER, 0x44, "RecipeBookRemove");
public static final PacketType RECIPE_BOOK_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x45, "RecipeBookSettings");
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x46, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x47, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x48, "ResetScore", "ResetScorePacket");
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x49, "ResourcePackPop", "ResourcePackPopPacket");
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x4A, "ResourcePackPush", "ResourcePackPushPacket");
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x4B, "Respawn", "SPacketRespawn");
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x4C, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x4D, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x4E, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x4F, "ServerData");
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x50, "SetActionBarText");
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x51, "SetBorderCenter");
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x52, "SetBorderLerpSize");
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x53, "SetBorderSize");
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x54, "SetBorderWarningDelay");
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x55, "SetBorderWarningDistance");
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x56, "SetCamera", "Camera", "SPacketCamera");
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x57, "SetChunkCacheCenter", "ViewCentre");
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x58, "SetChunkCacheRadius", "ViewDistance");
public static final PacketType SET_CURSOR_ITEM = new PacketType(PROTOCOL, SENDER, 0x59, "SetCursorItem");
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x5A, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5B, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x5C, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x5D, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x5E, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x60, "SetExperience", "Experience", "SPacketSetExperience");
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x61, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x62, "SetHeldSlot", "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x63, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x64, "SetPassengers", "Mount", "SPacketSetPassengers");
public static final PacketType SET_PLAYER_INVENTORY = new PacketType(PROTOCOL, SENDER, 0x65, "SetPlayerInventory");
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x66, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x67, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x68, "SetSimulationDistance");
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x69, "SetSubtitleText");
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x6A, "SetTime", "UpdateTime", "SPacketTimeUpdate");
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x6B, "SetTitleText");
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x6C, "SetTitlesAnimation");
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x6D, "SoundEntity", "EntitySound", "SPacketSoundEffect");
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x6E, "Sound", "NamedSoundEffect");
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x6F, "StartConfiguration");
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x70, "StopSound");
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x71, "StoreCookie");
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x72, "SystemChat");
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x73, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x74, "TagQuery", "NBTQuery");
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x75, "TakeItemEntity", "Collect", "SPacketCollectItem");
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x76, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
public static final PacketType TEST_INSTANCE_BLOCK_STATUS = new PacketType(PROTOCOL, SENDER, 0x77, "ClientboundTestInstanceBlockStatus");
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x78, "TickingState", "TickingStatePacket");
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x79, "TickingStep", "TickingStepPacket");
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x7A, "Transfer");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x7B, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x7C, "UpdateAttributes", "SPacketEntityProperties");
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x7D, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x7E, "UpdateRecipes", "RecipeUpdate");
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x7F, "UpdateTags", "Tags");
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x80, "ProjectilePower");
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x81, "CustomReportDetails");
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x82, "ServerLinks");
public static final PacketType TRACKED_WAYPOINT = new PacketType(PROTOCOL, SENDER, 0x83, "TrackedWaypoint");
public static final PacketType CLEAR_DIALOG = new PacketType(PROTOCOL, SENDER, 0x84, "ClearDialog");
public static final PacketType SHOW_DIALOG = new PacketType(PROTOCOL, SENDER, 0x85, "ShowDialog");
public static final PacketType DEBUG_BLOCK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugBlockValue");
public static final PacketType DEBUG_CHUNK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugChunkValue");
public static final PacketType DEBUG_ENTITY_VALUE = new PacketType(PROTOCOL, SENDER, 0x1C, "DebugEntityValue");
public static final PacketType DEBUG_EVENT = new PacketType(PROTOCOL, SENDER, 0x1D, "DebugEvent");
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1E, "DebugSample");
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1F, "DeleteChat");
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x20, "Disconnect", "KickDisconnect", "SPacketDisconnect");
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x21, "DisguisedChat");
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");
/**
* @deprecated Removed in 1.9
@@ -416,7 +423,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
*/
@Deprecated
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 232, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
private static final Server INSTANCE = new Server();
@@ -439,71 +445,74 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
private static final Sender SENDER = Sender.CLIENT;
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "AcceptTeleportation", "TeleportAccept", "CPacketConfirmTeleport");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x02, "SelectBundleItem");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x03, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeGameMode");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x06, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x08, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x09, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0A, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0B, "ClientCommand", "CPacketClientStatus");
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientTickEnd");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0E, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0F, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x10, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x14, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x15, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x17, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x18, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x19, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1A, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1B, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1C, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1D, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x21, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x23, "PickItemFromBlock");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromEntity", "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x25, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x26, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x27, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerCommand", "EntityAction", "CPacketEntityAction");
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerInput", "SteerVehicle", "CPacketInput");
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerLoaded");
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2C, "Pong");
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x2D, "RecipeBookChangeSettings", "RecipeSettings");
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x2E, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x2F, "RenameItem", "ItemName");
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x30, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x31, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x32, "SelectTrade", "TrSel");
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x33, "SetBeacon", "Beacon");
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x34, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x35, "SetCommandBlock");
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x36, "SetCommandMinecart");
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x37, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x38, "SetJigsawBlock", "SetJigsaw");
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x39, "SetStructureBlock", "Struct");
public static final PacketType SET_TEST_BLOCK = new PacketType(PROTOCOL, SENDER, 0x3A, "SetTestBlock");
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x3B, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x3C, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x3D, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType TEST_INSTANCE_BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x3E, "TestInstanceBlockAction");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x3F, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x40, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x41, "CustomClickAction");
public static final PacketType ATTACK = new PacketType(PROTOCOL, SENDER, 0x01, "Attack");
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x02, "BlockEntityTagQuery", "TileNBTQuery");
public static final PacketType SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x03, "SelectBundleItem");
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeDifficulty", "DifficultyChange");
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x05, "ChangeGameMode");
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ChatAck");
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommand");
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x08, "ChatCommandSigned");
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x09, "Chat", "CPacketChatMessage");
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x0A, "ChatSessionUpdate");
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchReceived");
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientCommand", "CPacketClientStatus");
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientTickEnd");
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientInformation", "Settings", "CPacketClientSettings");
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x10, "ConfigurationAcknowledged");
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClick", "WindowClick", "CPacketClickWindow");
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x15, "CookieResponse");
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x16, "CustomPayload", "CPacketCustomPayload");
public static final PacketType DEBUG_SUBSCRIPTION_REQUEST = new PacketType(PROTOCOL, SENDER, 0x17, "DebugSubscriptionRequest");
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x18, "EditBook", "BEdit");
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x19, "EntityTagQuery", "EntityNBTQuery");
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x1A, "Interact", "UseEntity", "CPacketUseEntity");
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1B, "JigsawGenerate");
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1C, "KeepAlive", "CPacketKeepAlive");
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1D, "LockDifficulty", "DifficultyLock");
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x21, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x23, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromBlock");
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x25, "PickItemFromEntity", "PickItem");
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x26, "PingRequest");
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x27, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerCommand", "EntityAction", "CPacketEntityAction");
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 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");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x43, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x44, "CustomClickAction");
/**
* @deprecated Removed in 1.17
@@ -523,6 +532,12 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
@Deprecated
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 253, "ChatPreview");
/**
* deprecated Removed in 1.21.9
*/
@Deprecated
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
/**
* @deprecated Renamed to USE_ITEM_ON
*/
@@ -713,6 +728,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
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");
/**
* @deprecated Removed in 1.20.4: replaced with new packets for removing and sending resource packs
@@ -745,6 +761,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType RESOURCE_PACK_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ResourcePack");
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x07, "ServerboundSelectKnownPacks");
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "CustomClickAction");
public static final PacketType ACCEPT_CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x09, "AcceptCodeOfConduct");
private static final Client INSTANCE = new Client();
@@ -35,12 +35,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.21.8";
public static final String MAXIMUM_MINECRAFT_VERSION = "26.2";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.21.8) was released.
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (26.2) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2025-07-17";
public static final String MINECRAFT_LAST_RELEASE_DATE = "2026-06-16";
private static Plugin plugin;
private static ProtocolConfig config;
@@ -27,6 +27,7 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
@@ -274,7 +275,7 @@ public class AsyncFilterManager implements AsynchronousManager {
* @return TRUE if we are, FALSE otherwise.
*/
private boolean onMainThread() {
return Thread.currentThread().getId() == mainThread.getId();
return Thread.currentThread() == mainThread;
}
@Override
@@ -347,8 +348,8 @@ public class AsyncFilterManager implements AsynchronousManager {
* Construct a asynchronous marker with all the default values.
* @return Asynchronous marker.
*/
public AsyncMarker createAsyncMarker() {
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
public AsyncMarker createAsyncMarker(Injector injector) {
return createAsyncMarker(injector, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
}
/**
@@ -356,13 +357,13 @@ public class AsyncFilterManager implements AsynchronousManager {
* @param timeoutDelta - how long (in ms) until the packet expire.
* @return An async marker.
*/
public AsyncMarker createAsyncMarker(long timeoutDelta) {
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
public AsyncMarker createAsyncMarker(Injector injector, long timeoutDelta) {
return createAsyncMarker(injector, timeoutDelta, currentSendingIndex.incrementAndGet());
}
// Helper method
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
private AsyncMarker createAsyncMarker(Injector injector, long timeoutDelta, long sendingIndex) {
return new AsyncMarker(injector, sendingIndex, System.currentTimeMillis(), timeoutDelta);
}
@Override
@@ -538,7 +538,7 @@ public class AsyncListenerHandler {
*/
private void listenerLoop(int workerID) {
// Danger, danger!
if (Thread.currentThread().getId() == mainThread.getId())
if (Thread.currentThread() == mainThread)
throw new IllegalStateException("Do not call this method from the main thread.");
if (cancelled)
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
@@ -23,14 +23,18 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -64,7 +68,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
/**
* The packet stream responsible for transmitting the packet when it's done processing.
*/
private transient PacketStream packetStream;
private final Injector injector;
/**
* Current list of async packet listeners.
@@ -86,7 +90,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
private volatile boolean processed;
// Whether or not the packet has been sent
private volatile boolean transmitted;
private final AtomicBoolean transmitted = new AtomicBoolean(false);
// Whether or not the asynchronous processing itself should be cancelled
private volatile boolean asyncCancelled;
@@ -109,11 +113,8 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* Create a container for asyncronous packets.
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
*/
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
if (packetStream == null)
throw new IllegalArgumentException("packetStream cannot be NULL");
this.packetStream = packetStream;
AsyncMarker(Injector injector, long sendingIndex, long initialTime, long timeoutDelta) {
this.injector = Objects.requireNonNull(injector, "injector is nul");
// Timeout
this.initialTime = initialTime;
@@ -180,16 +181,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* Retrieve the packet stream responsible for transmitting this packet.
* @return The packet stream.
*/
@Deprecated
public PacketStream getPacketStream() {
return packetStream;
return ProtocolLibrary.getProtocolManager();
}
/**
* Sets the output packet stream responsible for transmitting this packet.
* @param packetStream - new output packet stream.
*/
@Deprecated
public void setPacketStream(PacketStream packetStream) {
this.packetStream = packetStream;
// NOOP
}
/**
@@ -285,7 +288,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* @return TRUE if it has been sent before, FALSE otherwise.
*/
public boolean isTransmitted() {
return transmitted;
return transmitted.get();
}
/**
@@ -383,13 +386,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
* @throws IOException If the packet couldn't be sent.
*/
void sendPacket(PacketEvent event) throws IOException {
if (event.isServerPacket()) {
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
} else {
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
false);
if (!this.transmitted.compareAndSet(false, true)) {
return;
}
Object handle = event.getPacket().getHandle();
if (event.isServerPacket()) {
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
this.injector.sendClientboundPacket(handle, marker, false);
} else {
this.injector.readServerboundPacket(handle);
}
transmitted = true;
}
/**
@@ -55,6 +55,7 @@ import com.comphenix.protocol.wrappers.WrappedLevelChunkData;
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.WrappedProfilePublicKey;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.WrappedRegistrable;
@@ -65,6 +66,7 @@ import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedStatistic;
import com.comphenix.protocol.wrappers.WrappedTeamParameters;
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;
@@ -418,6 +420,16 @@ 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>
@@ -597,9 +609,7 @@ public abstract class AbstractStructure {
*/
public StructureModifier<List<PlayerInfoData>> getPlayerInfoDataLists() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(PlayerInfoData.getConverter()));
return getLists(PlayerInfoData.getConverter());
}
/**
@@ -804,6 +814,20 @@ 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.
@@ -819,6 +843,15 @@ 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.
@@ -1297,6 +1330,20 @@ 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}.
*
* @return A modifier for PositionMoveRotation fields.
*/
public StructureModifier<WrappedPositionMoveRotation> getPositionMoveRotation() {
return structureModifier.withType(
MinecraftReflection.getPositionMoveRotationClass(),
WrappedPositionMoveRotation.getConverter()
);
}
/**
* Represents an equivalent converter for ItemStack arrays.
* @author Kristian
@@ -232,13 +232,13 @@ public abstract class PacketAdapter implements PacketListener {
@Override
public void onPacketReceiving(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
throw new IllegalStateException("Override onPacketReceiving to get notifications of received packets!");
}
@Override
public void onPacketSending(PacketEvent event) {
// Lets prevent some bugs
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
throw new IllegalStateException("Override onPacketSending to get notifications of sent packets!");
}
@Override
@@ -0,0 +1,93 @@
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);
}
}
@@ -43,6 +43,7 @@ import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.PacketListenerSet;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector;
import com.comphenix.protocol.injector.packet.PacketRegistry;
@@ -151,29 +152,20 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
if (!this.closed) {
// if we skip the packet events later when actually writing into the pipeline we at least notify all
// monitor listeners before doing so - they will not be able to change the event tho
if (!filters) {
// ensure we are on the main thread if any listener requires that
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
NetworkMarker copy = marker; // okay fine
ProtocolLibrary.getScheduler().scheduleSyncDelayedTask(
() -> this.sendServerPacket(receiver, packet, copy, false), 1L);
return;
}
// construct the event and post to all monitor listeners
if (this.closed) {
return;
}
// A monitor listener should never modify a packet/event so we can simply invoke monitor listeners
// independently of our injector pipeline
if (!filters) {
this.runMonitorListeners(packet, () -> {
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false);
this.outboundListeners.invoke(event, ListenerPriority.MONITOR);
// update the marker of the event without accidentally constructing it
marker = NetworkMarker.getNetworkMarker(event);
}
// process outbound
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
});
}
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
}
@Override
@@ -200,34 +192,40 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
@Override
public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) {
if (!this.closed) {
// make sure we are on the main thread if any listener of the packet needs it
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
ProtocolLibrary.getScheduler().runTask(
() -> this.receiveClientPacket(sender, packet, marker, filters));
if (this.closed) {
return;
}
// make sure we are on the main thread if any listener of the packet needs it
if (filters && this.requiresMainThread(packet)) {
ProtocolLibrary.getScheduler().runTask(
() -> this.receiveClientPacket(sender, packet, marker, filters));
return;
}
Object nmsPacket = packet.getHandle();
if (filters) {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
this.invokeInboundPacketListeners(event);
if (event.isCancelled()) {
return;
}
Object nmsPacket = packet.getHandle();
// check to which listeners we need to post the packet
if (filters) {
// post to all listeners
PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender);
this.invokeInboundPacketListeners(event);
if (event.isCancelled()) {
return;
}
// prevent possible de-sync
nmsPacket = event.getPacket().getHandle();
} else {
// Prevent possible de-sync if the packet was replaced by a listener.
nmsPacket = event.getPacket().getHandle();
} else {
// A monitor listener should never modify a packet/event so we can simply invoke monitor listeners
// independently of our injector pipeline
this.runMonitorListeners(packet, () -> {
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
this.inboundListeners.invoke(event, ListenerPriority.MONITOR);
}
// post to the player inject, reset our cancel state change
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
});
}
// post to the player inject, reset our cancel state change
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
}
@Override
@@ -516,12 +514,25 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
this.postPacketToListeners(this.outboundListeners, event, true);
}
}
private boolean requiresMainThread(PacketContainer packet) {
return this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread();
}
private void runMonitorListeners(PacketContainer packet, Runnable notifyMonitor) {
if (this.requiresMainThread(packet)) {
ProtocolLibrary.getScheduler().runTask(notifyMonitor);
} else {
notifyMonitor.run();
}
}
private void postPacketToListeners(PacketListenerSet listeners, PacketEvent event, boolean outbound) {
try {
// append async marker if any async listener for the packet was registered
if (this.asyncFilterManager.hasAsynchronousListeners(event)) {
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker());
Injector injector = this.networkManagerInjector.getInjector(event.getPlayer());
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker(injector));
}
// post to sync listeners
@@ -287,8 +287,8 @@ public class StructureCache {
if (registryByteBuf.isPresent()) {
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(serializerBase, true).getConstructor(FuzzyMethodContract.newBuilder()
.parameterDerivedOf(ByteBuf.class)
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
.parameterExactType(ByteBuf.class)
.parameterExactType(MinecraftReflection.getRegistryAccessClass())
.build()));
TRICKED_DATA_SERIALIZER_BASE = (buf) -> accessor.invoke(buf, MinecraftRegistryAccess.get());
} else {
@@ -58,11 +58,13 @@ public class OutboundPacketListenerSet extends PacketListenerSet {
}
}
if (packets.iterator().hasNext()) {
event.getPacket().getPacketBundles().write(0, outPackets);
} else {
// cancel entire packet if each individual packet has been cancelled
event.setCancelled(true);
if (!event.isReadOnly()) {
if (packets.iterator().hasNext()) {
event.getPacket().getPacketBundles().write(0, outPackets);
} else {
// cancel entire packet if each individual packet has been cancelled
event.setCancelled(true);
}
}
}
}
@@ -29,6 +29,7 @@ import com.comphenix.protocol.injector.NetworkProcessor;
import com.comphenix.protocol.injector.netty.Injector;
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.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
@@ -242,7 +243,7 @@ public class NettyChannelInjector implements Injector {
this.uninject();
// remove any outgoing references
this.channel.attr(INJECTOR).remove();
this.channel.attr(INJECTOR).set(null);
// cleanup
this.savedMarkers.clear();
@@ -343,14 +344,15 @@ public class NettyChannelInjector implements Injector {
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.player != null) {
if (this.player != null && !(this.player instanceof TemporaryPlayer)) {
return this.player;
}
// check if the name of the player is already known to the injector
if (this.playerName != null) {
this.player = Bukkit.getPlayerExact(this.playerName);
if (this.player != null) {
Player bukkitPlayer = Bukkit.getPlayerExact(this.playerName);
if (bukkitPlayer != null) {
this.player = bukkitPlayer;
this.injectionFactory.cacheInjector(this.player, this);
}
}
@@ -5,6 +5,7 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelProgressivePromise;
@@ -33,6 +34,11 @@ final class NettyChannelProxy implements Channel {
this.injector = injector;
}
@Override
public ChannelId id() {
return this.delegate.id();
}
@Override
public EventLoop eventLoop() {
return this.eventLoop;
@@ -238,6 +244,11 @@ final class NettyChannelProxy implements Channel {
return this.delegate.attr(key);
}
@Override
public <T> boolean hasAttr(AttributeKey<T> attributeKey) {
return this.delegate.hasAttr(attributeKey);
}
@Override
public int compareTo(@NotNull Channel o) {
return this.delegate.compareTo(o);
@@ -10,11 +10,13 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import io.netty.channel.AbstractEventLoop;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.AbstractEventExecutor;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
@@ -25,7 +27,7 @@ import io.netty.util.concurrent.ScheduledFuture;
* An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which
* schedule something on the event loop to methods which decide what should happen to the scheduled task.
*/
final class NettyEventLoopProxy implements EventLoop {
final class NettyEventLoopProxy extends AbstractEventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
@@ -236,6 +238,15 @@ final class NettyEventLoopProxy implements EventLoop {
}
}
// ShreddedPaper uses this method
@Override
public void lazyExecute(Runnable command) {
Runnable proxied = this.proxyRunnable(command);
if (proxied != null) {
((AbstractEventExecutor)this.delegate).lazyExecute(proxied);
}
}
@Override
public void forEach(Consumer<? super EventExecutor> action) {
this.delegate.forEach(action);
@@ -251,6 +262,12 @@ final class NettyEventLoopProxy implements EventLoop {
return this.delegate.register(channel);
}
@Override
public ChannelFuture register(ChannelPromise channelPromise) {
return this.delegate.register(channelPromise);
}
@Deprecated
@Override
public ChannelFuture register(Channel channel, ChannelPromise promise) {
return this.delegate.register(channel, promise);
@@ -412,8 +412,8 @@ public class PacketRegistry {
private static Class<?> searchForPacket(List<String> classNames) {
for (String name : classNames) {
try {
Class<?> clazz = MinecraftReflection.getMinecraftClass(name);
if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
Class<?> clazz = resolvePacketClassName(name);
if (clazz != null && MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
&& !Modifier.isAbstract(clazz.getModifiers())) {
return clazz;
}
@@ -424,6 +424,22 @@ public class PacketRegistry {
return null;
}
/**
* Resolve a single packet class name. Fully-qualified names (those already containing their
* package, e.g. {@code net.minecraft.network.protocol.game.XPacket}) are loaded directly, since
* {@link MinecraftReflection#getMinecraftClass(String)} would otherwise prepend the Minecraft
* package and fail to find them. Relative names continue to be resolved against the Minecraft
* package.
*/
private static Class<?> resolvePacketClassName(String name) {
Optional<Class<?>> direct = MinecraftReflection.getOptionalClass(name);
if (direct.isPresent()) {
return direct.get();
}
return MinecraftReflection.getMinecraftClass(name);
}
/**
* Retrieves the correct packet class from a given type.
*
@@ -88,20 +88,23 @@ final class CachedPackage {
* @throws RuntimeException If we are unable to find the given class.
*/
public Optional<Class<?>> getPackageClass(String className, String... aliases) {
return cache.computeIfAbsent(className, x -> {
Optional<Class<?>> clazz = resolveClass(className);
if (clazz.isPresent()) {
return clazz;
}
Optional<Class<?>> cached = cache.get(className);
if (cached != null) {
return cached;
}
Optional<Class<?>> clazz = resolveClass(className);
if (!clazz.isPresent()) {
for (String alias : aliases) {
clazz = resolveClass(alias);
if (clazz.isPresent()) {
return clazz;
Optional<Class<?>> aliasClazz = resolveClass(alias);
if (aliasClazz.isPresent()) {
clazz = aliasClazz;
break;
}
}
}
return Optional.empty();
});
cache.put(className, clazz);
return clazz;
}
}
@@ -96,6 +96,11 @@ public final class MinecraftProtocolVersion {
map.put(new MinecraftVersion(1, 21, 2), 768);
map.put(new MinecraftVersion(1, 21, 6), 771);
map.put(new MinecraftVersion(1, 21, 8), 772);
map.put(new MinecraftVersion(1, 21, 9), 773);
map.put(new MinecraftVersion(1, 21, 11), 774);
map.put(new MinecraftVersion(26, 1, 0), 775);
map.put(new MinecraftVersion(26, 2, 0), 776);
return map;
}
@@ -553,6 +553,10 @@ public final class MinecraftReflection {
return getClass("com.mojang.authlib.GameProfile");
}
public static Class<?> getGameProfilePropertyMapClass() {
return getClass("com.mojang.authlib.properties.PropertyMap");
}
/**
* Retrieve the entity (NMS) class.
*
@@ -917,15 +921,19 @@ public final class MinecraftReflection {
}
public static Class<?> getMinecraftKeyClass() {
return getMinecraftClass("resources.MinecraftKey", "resources.ResourceLocation", "MinecraftKey");
return getMinecraftClass("resources.MinecraftKey", "resources.Identifier", "resources.ResourceLocation", "MinecraftKey");
}
public static Class<?> getMobEffectListClass() {
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect");
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect", "world.effect.MobEffects");
}
public static Class<?> getDamageTypeClass() {
return getMinecraftClass("world.damagesource.DamageType");
}
public static Class<?> getSoundEffectClass() {
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect");
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect", "sounds.SoundEvents");
}
/**
@@ -1114,6 +1122,15 @@ public final class MinecraftReflection {
return getOptionalNMS("network.chat.numbers.StyledFormat");
}
/**
* Retrieve the NMS TeamColor class, added in 26.2.
*
* @return The TeamColor class.
*/
public static Optional<Class<?>> getTeamColorClass() {
return getOptionalNMS("world.scores.TeamColor");
}
/**
* Retrieve the Gson class used by Minecraft.
*
@@ -1476,6 +1493,18 @@ public final class MinecraftReflection {
.orElseThrow(() -> new RuntimeException("Cannot find class " + className));
}
/**
* Attempt to load a class by its fully-qualified canonical name, without prepending the
* Minecraft package. Unlike {@link #getMinecraftClass(String)}, this can resolve names that
* already include their package (e.g. {@code net.minecraft.network.protocol.game.XPacket}).
*
* @param className - the fully-qualified class name.
* @return Optional that may contain the class.
*/
public static Optional<Class<?>> getOptionalClass(String className) {
return getClassSource().loadClass(className);
}
/**
* Retrieve the class object of a specific CraftBukkit class.
*
@@ -1680,7 +1709,7 @@ public final class MinecraftReflection {
}
public static Class<?> getEntityTypes() {
return getMinecraftClass("world.entity.EntityTypes", "world.entity.EntityType", "EntityTypes");
return getMinecraftClass("world.entity.EntityType", "world.entity.EntityTypes", "EntityTypes");
}
public static Class<?> getParticleParam() {
@@ -1899,4 +1928,13 @@ public final class MinecraftReflection {
return isMojangMapped;
}
/**
* Retrieves the PositionMoveRotation class (introduced in 1.21.2).
*
* @return The PositionMoveRotation class.
*/
public static Class<?> getPositionMoveRotationClass() {
return getMinecraftClass("world.entity.PositionMoveRotation");
}
}
@@ -36,6 +36,31 @@ import org.bukkit.Server;
* @author Kristian
*/
public final class MinecraftVersion implements Comparable<MinecraftVersion>, Serializable {
/**
* Version 26.2 - chaos cubed
*/
public static final MinecraftVersion v26_2 = new MinecraftVersion("26.2");
/**
* Version 26.1 - tiny takeover
*/
public static final MinecraftVersion v26_1 = new MinecraftVersion("26.1");
/**
* Version 1.21.11 - mounts of mayhem
*/
public static final MinecraftVersion v1_21_11 = new MinecraftVersion("1.21.11");
/**
* Version 1.21.10 - hotfix for 1.21.9
*/
public static final MinecraftVersion v1_21_10 = new MinecraftVersion("1.21.10");
/**
* Version 1.21.9 - the copper age drop
*/
public static final MinecraftVersion v1_21_9 = new MinecraftVersion("1.21.9");
/**
* Version 1.21.6 - chase the skies
*/
@@ -169,7 +194,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
/**
* The latest release version of minecraft.
*/
public static final MinecraftVersion LATEST = v1_21_6;
public static final MinecraftVersion LATEST = v26_2;
// used when serializing
private static final long serialVersionUID = -8695133558996459770L;
@@ -0,0 +1,3 @@
package com.comphenix.protocol.utility;
public record Tuple<A, B>(A first, B second) {}
@@ -2,12 +2,14 @@ package com.comphenix.protocol.utility;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufProcessor;
import io.netty.util.ByteProcessor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.Charset;
@@ -54,6 +56,16 @@ public class ZeroBuffer extends ByteBuf {
return false;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public ByteBuf asReadOnly() {
return null;
}
@Override
public int readerIndex() {
return 0;
@@ -179,36 +191,71 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public short getShortLE(int i) {
return 0;
}
@Override
public int getUnsignedShort(int i) {
return 0;
}
@Override
public int getUnsignedShortLE(int i) {
return 0;
}
@Override
public int getMedium(int i) {
return 0;
}
@Override
public int getMediumLE(int i) {
return 0;
}
@Override
public int getUnsignedMedium(int i) {
return 0;
}
@Override
public int getUnsignedMediumLE(int i) {
return 0;
}
@Override
public int getInt(int i) {
return 0;
}
@Override
public int getIntLE(int i) {
return 0;
}
@Override
public long getUnsignedInt(int i) {
return 0;
}
@Override
public long getUnsignedIntLE(int i) {
return 0;
}
@Override
public long getLong(int i) {
return 0;
}
@Override
public long getLongLE(int i) {
return 0;
}
@Override
public char getChar(int i) {
return 0;
@@ -264,6 +311,16 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public int getBytes(int i, FileChannel fileChannel, long l, int i1) throws IOException {
return 0;
}
@Override
public CharSequence getCharSequence(int i, int i1, Charset charset) {
return "";
}
@Override
public ByteBuf setBoolean(int i, boolean b) {
return null;
@@ -279,21 +336,41 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf setShortLE(int i, int i1) {
return null;
}
@Override
public ByteBuf setMedium(int i, int i1) {
return null;
}
@Override
public ByteBuf setMediumLE(int i, int i1) {
return null;
}
@Override
public ByteBuf setInt(int i, int i1) {
return null;
}
@Override
public ByteBuf setIntLE(int i, int i1) {
return null;
}
@Override
public ByteBuf setLong(int i, long l) {
return null;
}
@Override
public ByteBuf setLongLE(int i, long l) {
return null;
}
@Override
public ByteBuf setChar(int i, int i1) {
return null;
@@ -349,11 +426,21 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public int setBytes(int i, FileChannel fileChannel, long l, int i1) {
return 0;
}
@Override
public ByteBuf setZero(int i, int i1) {
return null;
}
@Override
public int setCharSequence(int i, CharSequence charSequence, Charset charset) {
return 0;
}
@Override
public boolean readBoolean() {
return false;
@@ -374,36 +461,71 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public short readShortLE() {
return 0;
}
@Override
public int readUnsignedShort() {
return 0;
}
@Override
public int readUnsignedShortLE() {
return 0;
}
@Override
public int readMedium() {
return 0;
}
@Override
public int readMediumLE() {
return 0;
}
@Override
public int readUnsignedMedium() {
return 0;
}
@Override
public int readUnsignedMediumLE() {
return 0;
}
@Override
public int readInt() {
return 0;
}
@Override
public int readIntLE() {
return 0;
}
@Override
public long readUnsignedInt() {
return 0;
}
@Override
public long readUnsignedIntLE() {
return 0;
}
@Override
public long readLong() {
return 0;
}
@Override
public long readLongLE() {
return 0;
}
@Override
public char readChar() {
return 0;
@@ -429,6 +551,11 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf readRetainedSlice(int i) {
return null;
}
@Override
public ByteBuf readBytes(ByteBuf byteBuf) {
return null;
@@ -469,6 +596,16 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public CharSequence readCharSequence(int i, Charset charset) {
return "";
}
@Override
public int readBytes(FileChannel fileChannel, long l, int i) {
return 0;
}
@Override
public ByteBuf skipBytes(int i) {
return null;
@@ -489,21 +626,41 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf writeShortLE(int i) {
return null;
}
@Override
public ByteBuf writeMedium(int i) {
return null;
}
@Override
public ByteBuf writeMediumLE(int i) {
return null;
}
@Override
public ByteBuf writeInt(int i) {
return null;
}
@Override
public ByteBuf writeIntLE(int i) {
return null;
}
@Override
public ByteBuf writeLong(long l) {
return null;
}
@Override
public ByteBuf writeLongLE(long l) {
return null;
}
@Override
public ByteBuf writeChar(int i) {
return null;
@@ -559,11 +716,21 @@ public class ZeroBuffer extends ByteBuf {
return 0;
}
@Override
public int writeBytes(FileChannel fileChannel, long l, int i) {
return 0;
}
@Override
public ByteBuf writeZero(int i) {
return null;
}
@Override
public int writeCharSequence(CharSequence charSequence, Charset charset) {
return 0;
}
@Override
public int indexOf(int i, int i1, byte b) {
return 0;
@@ -585,22 +752,22 @@ public class ZeroBuffer extends ByteBuf {
}
@Override
public int forEachByte(ByteBufProcessor byteBufProcessor) {
public int forEachByte(ByteProcessor byteProcessor) {
return 0;
}
@Override
public int forEachByte(int i, int i1, ByteBufProcessor byteBufProcessor) {
public int forEachByte(int i, int i1, ByteProcessor byteProcessor) {
return 0;
}
@Override
public int forEachByteDesc(ByteBufProcessor byteBufProcessor) {
public int forEachByteDesc(ByteProcessor byteProcessor) {
return 0;
}
@Override
public int forEachByteDesc(int i, int i1, ByteBufProcessor byteBufProcessor) {
public int forEachByteDesc(int i, int i1, ByteProcessor byteProcessor) {
return 0;
}
@@ -619,16 +786,31 @@ public class ZeroBuffer extends ByteBuf {
return null;
}
@Override
public ByteBuf retainedSlice() {
return null;
}
@Override
public ByteBuf slice(int i, int i1) {
return null;
}
@Override
public ByteBuf retainedSlice(int i, int i1) {
return null;
}
@Override
public ByteBuf duplicate() {
return null;
}
@Override
public ByteBuf retainedDuplicate() {
return null;
}
@Override
public int nioBufferCount() {
return 0;
@@ -738,4 +920,14 @@ public class ZeroBuffer extends ByteBuf {
public ByteBuf retain() {
return null;
}
@Override
public ByteBuf touch() {
return null;
}
@Override
public ByteBuf touch(Object o) {
return null;
}
}
@@ -22,6 +22,7 @@ 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;
@@ -165,16 +166,67 @@ public class AutoWrapper<T> implements EquivalentConverter<T> {
nmsInstanceCreator = noArgs;
nmsDefaultArgs = NO_ARGS;
} else {
// 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);
// 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;
}
}
}
}
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
@@ -70,6 +70,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
@@ -873,6 +874,13 @@ public class BukkitConverters {
return ignoreNull(new EquivalentConverter<EntityType>() {
@Override
public Object getGeneric(EntityType specific) {
if (MinecraftVersion.v26_2.atOrAbove()) {
// EntityType.byString was removed in 26.2, resolve through the registry instead
WrappedRegistry registry = WrappedRegistry.getRegistry(MinecraftReflection.getEntityTypes());
NamespacedKey key = specific.getKey();
return registry.get(new MinecraftKey(key.getNamespace(), key.getKey()));
}
if (entityTypeFromName == null) {
Class<?> entityTypesClass = MinecraftReflection.getEntityTypes();
entityTypeFromName = Accessors.getMethodAccessor(
@@ -1428,6 +1436,67 @@ public class BukkitConverters {
});
}
// Lazily-resolved CraftDamageType reflection. volatile + DCL keeps the publish safe
// and avoids re-running fuzzy lookups in concurrent injection paths.
private static volatile MethodAccessor damageTypeToNms = null;
private static volatile MethodAccessor damageTypeFromNms = null;
private static FuzzyReflection craftDamageTypeFuzzy() {
return FuzzyReflection.fromClass(getCraftBukkitClass("damage.CraftDamageType"), false);
}
private static MethodAccessor resolveDamageTypeToNms() {
MethodAccessor accessor = damageTypeToNms;
if (accessor != null) return accessor;
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()));
damageTypeToNms = accessor;
return accessor;
}
}
private static MethodAccessor resolveDamageTypeFromNms() {
MethodAccessor accessor = damageTypeFromNms;
if (accessor != null) return accessor;
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()));
damageTypeFromNms = accessor;
return accessor;
}
}
public static EquivalentConverter<org.bukkit.damage.DamageType> getDamageTypeConverter() {
return ignoreNull(new EquivalentConverter<org.bukkit.damage.DamageType>() {
@Override
public Class<org.bukkit.damage.DamageType> getSpecificType() {
return org.bukkit.damage.DamageType.class;
}
@Override
public Object getGeneric(org.bukkit.damage.DamageType specific) {
return resolveDamageTypeToNms().invoke(null, specific);
}
@Override
public org.bukkit.damage.DamageType getSpecific(Object generic) {
return (org.bukkit.damage.DamageType) resolveDamageTypeFromNms().invoke(null, generic);
}
});
}
private static Class<?> dimensionManager;
private static FauxEnumConverter<Dimension> dimensionConverter;
private static FauxEnumConverter<DimensionImpl> dimensionImplConverter;
@@ -46,8 +46,13 @@ 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 SERIALIZE_PAYLOAD_METHOD;
/** True when running on MC 1.21.5+ where DiscardedPayload is used instead of the ByteBuddy proxy. */
private static final boolean USE_DISCARDED_PAYLOAD;
private static final EquivalentConverter<CustomPacketPayloadWrapper> CONVERTER;
static {
@@ -55,22 +60,54 @@ public final class CustomPacketPayloadWrapper {
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
Method getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(MINECRAFT_KEY_CLASS)
.parameterCount(0)
.build());
GET_ID_PAYLOAD_METHOD = Accessors.getMethodAccessor(getPayloadId);
// 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;
boolean useDiscardedPayload = false;
try {
getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeExact(MINECRAFT_KEY_CLASS)
.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");
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;
USE_DISCARDED_PAYLOAD = useDiscardedPayload;
Method serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterDerivedOf(ByteBuf.class, 0)
.build());
SERIALIZE_PAYLOAD_METHOD = Accessors.getMethodAccessor(serializePayloadData);
// serialize method may not exist in new API
Method serializePayloadData = null;
try {
serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterDerivedOf(ByteBuf.class, 0)
.build());
} catch (IllegalArgumentException ignored) {
// Not present in new API
}
SERIALIZE_PAYLOAD_METHOD = serializePayloadData != null
? Accessors.getMethodAccessor(serializePayloadData) : null;
Constructor<?> payloadWrapperConstructor = makePayloadWrapper();
Constructor<?> payloadWrapperConstructor;
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);
} else {
payloadWrapperConstructor = makePayloadWrapper();
}
PAYLOAD_WRAPPER_CONSTRUCTOR = Accessors.getConstructorAccessor(payloadWrapperConstructor);
CONVERTER = new EquivalentConverter<CustomPacketPayloadWrapper>() {
@@ -171,8 +208,7 @@ public final class CustomPacketPayloadWrapper {
* @return a wrapper holding the minecraft key and payload of the given custom payload instance.
*/
public static CustomPacketPayloadWrapper fromUnknownPayload(Object payload) {
Object messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
MinecraftKey id = MinecraftKey.getConverter().getSpecific(messageId);
MinecraftKey id = getPayloadId(payload);
// 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
@@ -185,6 +221,12 @@ public final class CustomPacketPayloadWrapper {
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);
@@ -194,6 +236,17 @@ public final class CustomPacketPayloadWrapper {
return new CustomPacketPayloadWrapper(messagePayload, id);
}
private static MinecraftKey getPayloadId(Object payload) {
Object messageId;
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);
}
return MinecraftKey.getConverter().getSpecific(messageId);
}
/**
* Get the generic id of the wrapped message id.
*
@@ -230,6 +283,11 @@ public final class CustomPacketPayloadWrapper {
* @return a new payload wrapper instance using the provided message id and payload.
*/
public Object newHandle() {
if (USE_DISCARDED_PAYLOAD) {
// New API: DiscardedPayload(Identifier, ByteBuf)
ByteBuf buf = Unpooled.copiedBuffer(this.payload);
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), buf);
}
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), this.payload);
}
@@ -36,6 +36,12 @@ public abstract class EnumWrappers {
public enum ClientCommand {
PERFORM_RESPAWN,
REQUEST_STATS,
REQUEST_GAMERULE_VALUES,
/**
* @deprecated Removed in 26.1
*/
@Deprecated
OPEN_INVENTORY_ACHIEVEMENT
}
@@ -180,7 +186,8 @@ public abstract class EnumWrappers {
DROP_ALL_ITEMS,
DROP_ITEM,
RELEASE_USE_ITEM,
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND");
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND"),
STAB;
final String[] aliases;
PlayerDigType(String... aliases) {
@@ -717,6 +724,11 @@ 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;
@@ -745,6 +757,7 @@ 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;
@@ -805,7 +818,7 @@ public abstract class EnumWrappers {
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory", "sounds.SoundSource");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
@@ -821,13 +834,17 @@ public abstract class EnumWrappers {
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
if (MinecraftVersion.v26_1.atOrAbove()) {
// TODO
} else {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
}
} else {
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
@@ -863,6 +880,8 @@ 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());
@@ -890,6 +909,7 @@ 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());
@@ -1079,6 +1099,11 @@ 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);
@@ -1188,6 +1213,10 @@ 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)
@@ -0,0 +1,29 @@
package com.comphenix.protocol.wrappers;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
/**
* Mutable version of PropertyMap
* Credit to PaperMC: https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/io/papermc/paper/profile/MutablePropertyMap.java
*/
public class MutablePropertyMap extends PropertyMap {
private final Multimap<String, Property> properties = HashMultimap.create();
public MutablePropertyMap() {
super(ImmutableMultimap.of());
}
public MutablePropertyMap(final Multimap<String, Property> properties) {
this();
this.putAll(properties);
}
@Override
protected Multimap<String, Property> delegate() {
return this.properties;
}
}
@@ -0,0 +1,119 @@
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);
}
}
@@ -0,0 +1,86 @@
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) + '}';
}
}
@@ -0,0 +1,140 @@
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 + "}";
}
}
@@ -35,18 +35,30 @@ public class WrappedGameProfile extends AbstractWrapper {
GAME_PROFILE, String.class, String.class);
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class);
private static final ConstructorAccessor CREATE_UUID_STRING_PROPERTIES = Accessors.getConstructorAccessorOrNull(
GAME_PROFILE, UUID.class, String.class, MinecraftReflection.getGameProfilePropertyMapClass());
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAccessorOrNull(
GAME_PROFILE, "id", String.class);
private static final MethodAccessor GET_ID = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getId");
private static final MethodAccessor GET_NAME = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getName");
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "getProperties");
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAccessorOrNull(
GAME_PROFILE, "isComplete");
private static MethodAccessor GET_ID;
private static MethodAccessor GET_NAME;
private static MethodAccessor GET_PROPERTIES;
private static MethodAccessor IS_COMPLETE;
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");
}
}
// Fetching game profile
private static FieldAccessor PLAYER_PROFILE;
@@ -117,6 +129,51 @@ public class WrappedGameProfile extends AbstractWrapper {
this(parseUUID(id), name);
}
private static Object createHandle(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
if (CREATE_STRING_STRING != null) {
return CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name);
}
if (CREATE_UUID_STRING == null) {
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
}
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
return CREATE_UUID_STRING.invoke(uuid, name);
}
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
// it's not allowed to pass null for both, so we need to pre-check that
if (uuid == null && (name == null || name.isEmpty())) {
throw new IllegalArgumentException("Name and ID cannot both be blank");
}
// 1.21.9+ made PropertyMap's underlying map immutable, so we need to override it with a mutable map
if (MinecraftVersion.v1_21_9.atOrAbove()) {
return CREATE_UUID_STRING_PROPERTIES.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name,
convertPropertyMap(properties));
}
return CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name);
}
private static Object convertPropertyMap(Multimap<String, WrappedSignedProperty> properties) {
com.comphenix.protocol.wrappers.MutablePropertyMap map =
new com.comphenix.protocol.wrappers.MutablePropertyMap();
if (properties == null || properties.isEmpty()) {
return map;
}
for (String key : properties.keySet()) {
for (WrappedSignedProperty property : properties.get(key)) {
map.put(key, (com.mojang.authlib.properties.Property) property.getHandle());
}
}
return map;
}
/**
* Construct a new game profile with the given properties.
* <p>
@@ -127,24 +184,12 @@ public class WrappedGameProfile extends AbstractWrapper {
*/
public WrappedGameProfile(UUID uuid, String name) {
super(GAME_PROFILE);
setHandle(createHandle(uuid, name, null));
}
if (CREATE_STRING_STRING != null) {
setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name));
} else if (CREATE_UUID_STRING != null) {
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
// it's not allowed to pass null for both, so we need to pre-check that
if (uuid == null && (name == null || name.isEmpty())) {
throw new IllegalArgumentException("Name and ID cannot both be blank");
}
setHandle(CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name));
} else {
setHandle(CREATE_UUID_STRING.invoke(uuid, name));
}
} else {
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
}
public WrappedGameProfile(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
super(GAME_PROFILE);
setHandle(createHandle(uuid, name, properties));
}
/**
@@ -157,7 +202,12 @@ public class WrappedGameProfile extends AbstractWrapper {
if (handle == null)
return null;
return new WrappedGameProfile(handle);
WrappedGameProfile delegate = new WrappedGameProfile(handle);
if (MinecraftVersion.v1_21_9.atOrAbove()) {
return new WrappedGameProfile(delegate.getUUID(), delegate.getName(), delegate.getProperties());
} else {
return delegate;
}
}
/**
@@ -269,7 +319,15 @@ public class WrappedGameProfile extends AbstractWrapper {
if (result == null) {
Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle);
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(GuavaWrappers.getBukkitMultimap(properties)) {
Multimap inner;
if (MinecraftVersion.v1_21_9.atOrAbove()) {
inner = properties;
} else {
inner = GuavaWrappers.getBukkitMultimap(properties);
}
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(inner) {
@Override
protected Object toInner(WrappedSignedProperty outer) {
return outer.handle;
@@ -0,0 +1,95 @@
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 + '\''
+ '}';
}
}
@@ -0,0 +1,96 @@
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
+ '}';
}
}
@@ -0,0 +1,126 @@
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
+ '}';
}
}
@@ -0,0 +1,183 @@
package com.comphenix.protocol.wrappers;
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 org.bukkit.util.Vector;
/**
* Wrapper around the NMS {@code PositionMoveRotation} record, introduced in Minecraft 1.21.2.
* <p>
* This type is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC}
* to represent combined position, movement delta and rotation.
* <p>
* Example usage:
* <pre>{@code
* WrappedPositionMoveRotation posRot = WrappedPositionMoveRotation.create(
* new Vector(x, y, z), // position
* new Vector(0, 0, 0), // deltaMovement
* yaw, // yRot
* pitch // xRot
* );
* packet.getPositionMoveRotation().write(0, posRot);
* }</pre>
*/
public class WrappedPositionMoveRotation extends AbstractWrapper {
private static Class<?> NMS_CLASS;
private static ConstructorAccessor CONSTRUCTOR;
private static StructureModifier<Object> BASE_MODIFIER;
private final StructureModifier<Object> modifier;
private WrappedPositionMoveRotation(Object handle) {
super(getNmsClass());
setHandle(handle);
this.modifier = getBaseModifier().withTarget(handle);
}
private static Class<?> getNmsClass() {
if (NMS_CLASS == null) {
NMS_CLASS = MinecraftReflection.getPositionMoveRotationClass();
}
return NMS_CLASS;
}
private static StructureModifier<Object> getBaseModifier() {
if (BASE_MODIFIER == null) {
BASE_MODIFIER = new StructureModifier<>(getNmsClass());
}
return BASE_MODIFIER;
}
private static ConstructorAccessor getConstructor() {
if (CONSTRUCTOR == null) {
CONSTRUCTOR = Accessors.getConstructorAccessor(
getNmsClass(),
MinecraftReflection.getVec3DClass(),
MinecraftReflection.getVec3DClass(),
float.class,
float.class
);
}
return CONSTRUCTOR;
}
/**
* Creates a new {@code WrappedPositionMoveRotation} from the given NMS handle.
*
* @param handle - the NMS {@code PositionMoveRotation} instance.
* @return the wrapper.
*/
public static WrappedPositionMoveRotation fromHandle(Object handle) {
return new WrappedPositionMoveRotation(handle);
}
/**
* Creates a new {@code WrappedPositionMoveRotation} with the given values.
*
* @param position - the absolute position.
* @param deltaMovement - the movement delta (velocity).
* @param yRot - the yaw rotation.
* @param xRot - the pitch rotation.
* @return a new wrapper instance.
*/
public static WrappedPositionMoveRotation create(Vector position, Vector deltaMovement, float yRot, float xRot) {
EquivalentConverter<Vector> conv = BukkitConverters.getVectorConverter();
Object handle = getConstructor().invoke(
conv.getGeneric(position),
conv.getGeneric(deltaMovement),
yRot,
xRot
);
return new WrappedPositionMoveRotation(handle);
}
/**
* Retrieves the absolute position component.
*
* @return the position as a Bukkit {@link Vector}.
*/
public Vector getPosition() {
return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
}
/**
* Sets the absolute position component.
*
* @param position - the new position.
*/
public void setPosition(Vector position) {
modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(0, position);
}
/**
* Retrieves the movement delta (velocity) component.
*
* @return the delta movement as a Bukkit {@link Vector}.
*/
public Vector getDeltaMovement() {
return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(1);
}
/**
* Sets the movement delta (velocity) component.
*
* @param deltaMovement - the new delta movement.
*/
public void setDeltaMovement(Vector deltaMovement) {
modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(1, deltaMovement);
}
/**
* Retrieves the yaw rotation (y-axis rotation).
*
* @return the yaw as a float.
*/
public float getYRot() {
StructureModifier<Float> floats = modifier.withType(float.class);
return floats.read(0);
}
/**
* Sets the yaw rotation (y-axis rotation).
*
* @param yRot - the new yaw value.
*/
public void setYRot(float yRot) {
modifier.withType(float.class).write(0, yRot);
}
/**
* Retrieves the pitch rotation (x-axis rotation).
*
* @return the pitch as a float.
*/
public float getXRot() {
StructureModifier<Float> floats = modifier.withType(float.class);
return floats.read(1);
}
/**
* Sets the pitch rotation (x-axis rotation).
*
* @param xRot - the new pitch value.
*/
public void setXRot(float xRot) {
modifier.withType(float.class).write(1, xRot);
}
/**
* Returns an {@link EquivalentConverter} that converts between {@code WrappedPositionMoveRotation}
* and the underlying NMS handle.
*
* @return the converter.
*/
public static EquivalentConverter<WrappedPositionMoveRotation> getConverter() {
return Converters.handle(AbstractWrapper::getHandle,
WrappedPositionMoveRotation::fromHandle, WrappedPositionMoveRotation.class);
}
}
@@ -1,28 +1,48 @@
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
// map of NMS class to registry instance (static/built-in registries)
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;
@@ -78,13 +98,69 @@ public class WrappedRegistry {
}
}
}
} catch (ReflectiveOperationException ignored) {
} catch (ReflectiveOperationException ex) {
ProtocolLogger.debug("Failed to inspect built-in registry field " + field, ex);
}
}
}
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()
@@ -117,17 +193,53 @@ public class WrappedRegistry {
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.PUBLIC)
.build()));
} catch (IllegalArgumentException ignored) {
} catch (IllegalArgumentException ex) {
ProtocolLogger.debug("Failed to find Registry.getHolder method", ex);
}
}
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) {
@@ -155,6 +267,10 @@ 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);
}
@@ -170,7 +286,193 @@ 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) {
return REGISTRY.get(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;
}
}
@@ -0,0 +1,168 @@
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 + "}";
}
}
@@ -0,0 +1,122 @@
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 + '}';
}
}
@@ -3,6 +3,8 @@ package com.comphenix.protocol.wrappers;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -90,12 +92,26 @@ public class WrappedTeamParameters extends AbstractWrapper {
@NotNull
public EnumWrappers.ChatFormatting getColor() {
if (MinecraftVersion.v26_2.atOrAbove()) {
Optional<?> optional = modifier.<Optional<?>>withType(Optional.class).read(0);
Object teamColor = optional != null ? optional.orElse(null) : null;
if (teamColor == null) {
return EnumWrappers.ChatFormatting.RESET;
}
return EnumWrappers.ChatFormatting.valueOf(((Enum<?>) teamColor).name());
}
return modifier
.withType(EnumWrappers.getChatFormattingClass(), EnumWrappers.getChatFormattingConverter())
.read(0);
}
public int getOptions() {
if (MinecraftVersion.v26_2.atOrAbove()) {
return (byte) modifier.withType(byte.class).read(0);
}
return (int) modifier.withType(int.class).read(0);
}
@@ -210,9 +226,27 @@ public class WrappedTeamParameters extends AbstractWrapper {
wrapped.modifier.withType(String.class).writeSafely(1, collisionRule.toString());
}
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
wrapped.modifier.withType(int.class).write(0, options);
if (MinecraftVersion.v26_2.atOrAbove()) {
wrapped.modifier.withType(Optional.class).write(0, Optional.ofNullable(toNmsTeamColor(color)));
wrapped.modifier.withType(byte.class).write(0, (byte) options);
} else {
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
wrapped.modifier.withType(int.class).write(0, options);
}
return wrapped;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static Object toNmsTeamColor(EnumWrappers.ChatFormatting color) {
Class<?> teamColorClass = MinecraftReflection.getTeamColorClass()
.orElseThrow(() -> new IllegalStateException("TeamColor class doesn't exist on this server version"));
try {
return Enum.valueOf((Class) teamColorClass, color.name());
} catch (IllegalArgumentException ex) {
// formatting-only values (e.g. RESET) don't map to a team color
return null;
}
}
}
}
@@ -0,0 +1,194 @@
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 + "}";
}
}
@@ -5,8 +5,7 @@ import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.util.Base64;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
@@ -23,7 +22,7 @@ public class NbtTextSerializer {
* A default instance of this serializer.
*/
public static final NbtTextSerializer DEFAULT = new NbtTextSerializer();
private NbtBinarySerializer binarySerializer;
public NbtTextSerializer() {
@@ -59,7 +58,7 @@ public class NbtTextSerializer {
binarySerializer.serialize(value, dataOutput);
// Serialize that array
return Base64Coder.encodeLines(outputStream.toByteArray());
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
/**
@@ -70,7 +69,7 @@ public class NbtTextSerializer {
* @throws IOException If we are unable to parse the input.
*/
public <TType> NbtWrapper<TType> deserialize(String input) throws IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input));
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(input));
return binarySerializer.deserialize(new DataInputStream(inputStream));
}
@@ -7,6 +7,8 @@ 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.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.MinecraftGenerator;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
@@ -23,6 +25,7 @@ public final class ServerPingRecord implements ServerPingImpl {
private static Class<?> SERVER_PING;
private static Class<?> PLAYER_SAMPLE_CLASS;
private static Class<?> SERVER_DATA_CLASS;
private static Class<?> NAME_AND_ID_CLASS;
private static Class<?> GSON_CLASS;
private static MethodAccessor GSON_TO_JSON;
@@ -56,13 +59,37 @@ public final class ServerPingRecord implements ServerPingImpl {
PLAYER_SAMPLE_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
SERVER_DATA_CLASS = MinecraftReflection.getServerPingServerDataClass();
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING.getConstructors()[0]);
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), Optional.class, Optional.class, Optional.class, boolean.class);
DATA_WRAPPER = AutoWrapper.wrap(ServerData.class, SERVER_DATA_CLASS);
SAMPLE_WRAPPER = AutoWrapper.wrap(PlayerSample.class, PLAYER_SAMPLE_CLASS);
FAVICON_WRAPPER = AutoWrapper.wrap(Favicon.class, MinecraftReflection.getMinecraftClass("network.protocol.status.ServerPing$a", "network.protocol.status.ServerStatus$Favicon"));
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
if (MinecraftVersion.v1_21_10.atOrAbove()) {
NAME_AND_ID_CLASS = MinecraftReflection.getMinecraftClass("server.players.NameAndId");
NAME_AND_ID_WRAPPER = AutoWrapper.wrap(NameAndId.class, NAME_AND_ID_CLASS);
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(new EquivalentConverter<>() {
@Override
public Object getGeneric(WrappedGameProfile specific) {
NameAndId wrapper = new NameAndId(specific.getUUID(), specific.getName());
return NAME_AND_ID_WRAPPER.getGeneric(wrapper);
}
@Override
public WrappedGameProfile getSpecific(Object generic) {
NameAndId wrapper = NAME_AND_ID_WRAPPER.getSpecific(generic);
return new WrappedGameProfile(wrapper.id, wrapper.name);
}
@Override
public Class<WrappedGameProfile> getSpecificType() {
return WrappedGameProfile.class;
}
});
} else {
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
}
DEFAULT_DESCRIPTION = WrappedChatComponent.fromLegacyText("A Minecraft Server");
@@ -124,11 +151,24 @@ public final class ServerPingRecord implements ServerPingImpl {
}
}
public static final class NameAndId {
public UUID id;
public String name;
public NameAndId(UUID id, String name) {
this.id = id;
this.name = name;
}
public NameAndId() {
this(MinecraftGenerator.SYS_UUID, "");
}
}
private static AutoWrapper<PlayerSample> SAMPLE_WRAPPER;
private static AutoWrapper<ServerData> DATA_WRAPPER;
private static AutoWrapper<Favicon> FAVICON_WRAPPER;
private static AutoWrapper<NameAndId> NAME_AND_ID_WRAPPER;
private WrappedChatComponent description;
private PlayerSample playerSample;
@@ -0,0 +1,69 @@
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 + "}";
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,43 @@
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);
}
}
@@ -0,0 +1,42 @@
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);
}
}
@@ -0,0 +1,48 @@
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);
}
}
@@ -0,0 +1,39 @@
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);
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,27 @@
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.
}
@@ -0,0 +1,45 @@
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);
}
}
@@ -0,0 +1,79 @@
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);
}
}
@@ -0,0 +1,42 @@
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);
}
}
@@ -0,0 +1,84 @@
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;
}
}
}
@@ -0,0 +1,25 @@
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>.
}
@@ -0,0 +1,53 @@
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);
}
}
@@ -0,0 +1,45 @@
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);
}
}
@@ -0,0 +1,45 @@
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);
}
}
@@ -0,0 +1,44 @@
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);
}
}
@@ -0,0 +1,81 @@
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 }
}
@@ -0,0 +1,54 @@
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);
}
}
@@ -0,0 +1,45 @@
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);
}
}
@@ -0,0 +1,42 @@
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);
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,53 @@
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);
}
}
@@ -0,0 +1,42 @@
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);
}
}
@@ -0,0 +1,200 @@
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);
}
}
@@ -0,0 +1,79 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.World;
import org.bukkit.entity.Entity;
/**
* Wrapper for {@code ClientboundAnimatePacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int entityId} – entity ID performing the animation</li>
* <li>{@code int animationId} – animation ID:
* <ul>
* <li>0 = swing main arm</li>
* <li>1 = hurt</li>
* <li>2 = wake up</li>
* <li>3 = swing off hand</li>
* <li>4 = critical hit</li>
* <li>5 = magic critical hit</li>
* </ul>
* </li>
* </ul>
*/
public class WrappedClientboundAnimatePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.ANIMATION;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getEntityClass(), BukkitUnwrapper.getInstance())
.withParam(int.class);
public WrappedClientboundAnimatePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundAnimatePacket(int entityId, int animationId) {
this();
setEntityId(entityId);
setAnimationId(animationId);
}
public WrappedClientboundAnimatePacket(Entity entity, int animationId) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(entity, animationId)));
}
public WrappedClientboundAnimatePacket(PacketContainer packet) {
super(packet, TYPE);
}
public Entity getEntity(World world) {
return handle.getEntityModifier(world).readSafely(0);
}
public void setEntity(Entity entity) {
handle.getEntityModifier(entity.getWorld()).writeSafely(0, entity);
}
public int getEntityId() {
return handle.getIntegers().readSafely(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().writeSafely(0, entityId);
}
public int getAnimationId() {
return handle.getIntegers().readSafely(1);
}
public void setAnimationId(int animationId) {
handle.getIntegers().writeSafely(1, animationId);
}
}
@@ -0,0 +1,109 @@
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.reflect.EquivalentConverter;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedStatistic;
import java.util.HashMap;
import java.util.Map;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundAwardStatsPacket} (game phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Object2IntMap<Stat<?>> stats} – statistic → value map to award to the player</li>
* </ul>
*/
public class WrappedClientboundAwardStatsPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.STATISTIC;
/** The NMS field type is {@code Object2IntMap} – obtained lazily from the modifier. */
private static final Class<?> STATS_FIELD_TYPE;
/** NMS constructor accessor: {@code ClientboundAwardStatsPacket(Object2IntMap)}. */
private static final EquivalentConstructor CONSTRUCTOR;
static {
Class<?> fieldType = new PacketContainer(TYPE).getModifier().getField(0).getType();
STATS_FIELD_TYPE = fieldType;
CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(fieldType, createStatsConverter(fieldType));
}
/**
* Creates a converter that produces an Object2IntOpenHashMap (implementing Object2IntMap)
* from a {@code Map<WrappedStatistic, Integer>}, so it can be passed to the NMS constructor.
*/
@SuppressWarnings("unchecked")
private static EquivalentConverter<Map<WrappedStatistic, Integer>> createStatsConverter(Class<?> nmsMapClass) {
// reuse the ProtocolLib statistic converter for individual entries
EquivalentConverter<WrappedStatistic> statConverter =
com.comphenix.protocol.wrappers.BukkitConverters.getWrappedStatisticConverter();
return new EquivalentConverter<Map<WrappedStatistic, Integer>>() {
@Override
public Object getGeneric(Map<WrappedStatistic, Integer> specific) {
try {
// Create an Object2IntOpenHashMap (implements Object2IntMap)
Class<?> openHashMapClass = Class.forName(
"it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap");
Object nmsMap = openHashMapClass.getDeclaredConstructor().newInstance();
java.lang.reflect.Method put = openHashMapClass.getMethod(
"put", Object.class, int.class);
for (Map.Entry<WrappedStatistic, Integer> entry : specific.entrySet()) {
put.invoke(nmsMap, statConverter.getGeneric(entry.getKey()), (int) entry.getValue());
}
return nmsMap;
} catch (Exception e) {
throw new RuntimeException("Failed to convert stats map", e);
}
}
@Override
public Map<WrappedStatistic, Integer> getSpecific(Object generic) {
Map<Object, Object> nmsMap = (Map<Object, Object>) generic;
Map<WrappedStatistic, Integer> result = new HashMap<>();
for (Map.Entry<Object, Object> entry : nmsMap.entrySet()) {
result.put(statConverter.getSpecific(entry.getKey()),
((Number) entry.getValue()).intValue());
}
return result;
}
@Override
public Class<Map<WrappedStatistic, Integer>> getSpecificType() {
return (Class) Map.class;
}
};
}
public WrappedClientboundAwardStatsPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundAwardStatsPacket(Map<WrappedStatistic, Integer> stats) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(stats)));
}
public WrappedClientboundAwardStatsPacket(PacketContainer packet) {
super(packet, TYPE);
}
public Map<WrappedStatistic, Integer> getStats() {
return handle.getStatisticMaps().read(0);
}
public void setStats(Map<WrappedStatistic, Integer> stats) {
// ProtocolLib's getStatisticMaps() converter writes a plain HashMap back to the NMS field,
// but the field type is Object2IntMap (a fastutil interface) – the cast fails.
// Create the correct Object2IntOpenHashMap via our custom converter and write to the raw modifier.
Object nmsMap = createStatsConverter(STATS_FIELD_TYPE).getGeneric(stats);
handle.getModifier().write(0, nmsMap);
}
}
@@ -0,0 +1,41 @@
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 net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundBlockChangedAckPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int sequence} – sequence number of the block change acknowledgement</li>
* </ul>
*/
public class WrappedClientboundBlockChangedAckPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.BLOCK_CHANGED_ACK;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class);
public WrappedClientboundBlockChangedAckPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBlockChangedAckPacket(int sequence) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(sequence)));
}
public WrappedClientboundBlockChangedAckPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getSequence() {
return handle.getIntegers().read(0);
}
public void setSequence(int sequence) {
handle.getIntegers().write(0, sequence);
}
}
@@ -0,0 +1,67 @@
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.BlockPosition;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundBlockDestructionPacket} (Play phase, clientbound).
*
* <p>Controls the cracking/break-progress animation overlay on a block.
*
* <p>Packet structure:
* <ul>
* <li>{@code int id} – unique ID for this break action (used to track multiple simultaneous breakers)</li>
* <li>{@code BlockPos pos} – position of the block being broken</li>
* <li>{@code int destroyStage} – break progress stage (0–9; 10+ removes the overlay)</li>
* </ul>
*/
public class WrappedClientboundBlockDestructionPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.BLOCK_BREAK_ANIMATION;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class) // id
.withParam(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter()) // pos
.withParam(int.class);
public WrappedClientboundBlockDestructionPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBlockDestructionPacket(int id, BlockPosition pos, int destroyStage) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(id, pos, destroyStage)));
}
public WrappedClientboundBlockDestructionPacket(PacketContainer packet) {
super(packet, TYPE);
}
/** Returns the unique action ID used to differentiate concurrent break animations. */
public int getId() {
return handle.getIntegers().read(0);
}
public void setId(int id) {
handle.getIntegers().write(0, id);
}
public BlockPosition getPos() {
return handle.getBlockPositionModifier().read(0);
}
public void setPos(BlockPosition pos) {
handle.getBlockPositionModifier().write(0, pos);
}
/** Returns the destroy stage (0–9). Values ≥ 10 remove the animation overlay. */
public int getDestroyStage() {
return handle.getIntegers().read(1);
}
public void setDestroyStage(int destroyStage) {
handle.getIntegers().write(1, destroyStage);
}
}
@@ -0,0 +1,56 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.WrappedRegistrable;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundBlockEntityDataPacket} (game phase, clientbound).
*/
public class WrappedClientboundBlockEntityDataPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.TILE_ENTITY_DATA;
public WrappedClientboundBlockEntityDataPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBlockEntityDataPacket(BlockPosition pos, WrappedRegistrable blockEntityType, NbtBase tag) {
this();
setPos(pos);
setBlockEntityType(blockEntityType);
setTag(tag);
}
public WrappedClientboundBlockEntityDataPacket(PacketContainer packet) {
super(packet, TYPE);
}
public BlockPosition getPos() {
return handle.getBlockPositionModifier().read(0);
}
public void setPos(BlockPosition pos) {
handle.getBlockPositionModifier().write(0, pos);
}
public WrappedRegistrable getBlockEntityType() {
return handle.getBlockEntityTypeModifier().read(0);
}
public void setBlockEntityType(WrappedRegistrable type) {
handle.getBlockEntityTypeModifier().write(0, type);
}
public NbtBase getTag() {
return handle.getNbtModifier().read(0);
}
public void setTag(NbtBase tag) {
handle.getNbtModifier().write(0, tag);
}
}
@@ -0,0 +1,77 @@
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.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.Material;
/**
* Wrapper for {@code ClientboundBlockEventPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code BlockPosition pos} – position of the block</li>
* <li>{@code int actionId} – block action ID (block-type dependent)</li>
* <li>{@code int actionParam} – block action parameter (block-type dependent)</li>
* <li>{@code Material blockType} – type of block at the position</li>
* </ul>
*/
public class WrappedClientboundBlockEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.BLOCK_ACTION;
// NMS constructor order: (BlockPos pos, Block block, int b0, int b1)
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter())
.withParam(MinecraftReflection.getBlockClass(), BukkitConverters.getBlockConverter())
.withParam(int.class)
.withParam(int.class);
public WrappedClientboundBlockEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBlockEventPacket(BlockPosition pos, int actionId, int actionParam, Material blockType) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(pos, blockType, actionId, actionParam)));
}
public WrappedClientboundBlockEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
public BlockPosition getPos() {
return handle.getBlockPositionModifier().read(0);
}
public void setPos(BlockPosition pos) {
handle.getBlockPositionModifier().write(0, pos);
}
public int getActionId() {
return handle.getIntegers().read(0);
}
public void setActionId(int actionId) {
handle.getIntegers().write(0, actionId);
}
public int getActionParam() {
return handle.getIntegers().read(1);
}
public void setActionParam(int actionParam) {
handle.getIntegers().write(1, actionParam);
}
public Material getBlockType() {
return handle.getBlocks().read(0);
}
public void setBlockType(Material blockType) {
handle.getBlocks().write(0, blockType);
}
}
@@ -0,0 +1,55 @@
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.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundBlockUpdatePacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code BlockPosition pos} – position of the changed block</li>
* <li>{@code WrappedBlockData blockData} – new block state</li>
* </ul>
*/
public class WrappedClientboundBlockUpdatePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.BLOCK_CHANGE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter())
.withParam(MinecraftReflection.getIBlockDataClass(), BukkitConverters.getWrappedBlockDataConverter());
public WrappedClientboundBlockUpdatePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBlockUpdatePacket(BlockPosition pos, WrappedBlockData blockData) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(pos, blockData)));
}
public WrappedClientboundBlockUpdatePacket(PacketContainer packet) {
super(packet, TYPE);
}
public BlockPosition getPos() {
return handle.getBlockPositionModifier().read(0);
}
public void setPos(BlockPosition pos) {
handle.getBlockPositionModifier().write(0, pos);
}
public WrappedBlockData getBlockData() {
return handle.getBlockData().read(0);
}
public void setBlockData(WrappedBlockData blockData) {
handle.getBlockData().write(0, blockData);
}
}
@@ -0,0 +1,132 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import java.util.Objects;
import java.util.UUID;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.boss.BossBar;
import org.bukkit.craftbukkit.boss.CraftBossBar;
public class WrappedClientboundBossEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.BOSS;
private static final MethodAccessor CREATE_ADD_PACKET;
private static final MethodAccessor CREATE_REMOVE_PACKET;
private static final MethodAccessor CREATE_UPDATE_PROGRESS_PACKET;
private static final MethodAccessor CREATE_UPDATE_NAME_PACKET;
private static final MethodAccessor CREATE_UPDATE_STYLE_PACKET;
private static final MethodAccessor CREATE_UPDATE_PROPERTIES_PACKET;
private static final FieldAccessor BOSS_BAR_HANDLE;
static {
try {
Class<?> packetClass = MinecraftReflection.getMinecraftClass(
"network.protocol.game.ClientboundBossEventPacket");
Class<?> bossEventClass = MinecraftReflection.getMinecraftClass("world.BossEvent");
CREATE_ADD_PACKET = Accessors.getMethodAccessor(packetClass, "createAddPacket", bossEventClass);
CREATE_REMOVE_PACKET = Accessors.getMethodAccessor(packetClass, "createRemovePacket", UUID.class);
CREATE_UPDATE_PROGRESS_PACKET = Accessors.getMethodAccessor(packetClass, "createUpdateProgressPacket", bossEventClass);
CREATE_UPDATE_NAME_PACKET = Accessors.getMethodAccessor(packetClass, "createUpdateNamePacket", bossEventClass);
CREATE_UPDATE_STYLE_PACKET = Accessors.getMethodAccessor(packetClass, "createUpdateStylePacket", bossEventClass);
CREATE_UPDATE_PROPERTIES_PACKET = Accessors.getMethodAccessor(packetClass, "createUpdatePropertiesPacket", bossEventClass);
BOSS_BAR_HANDLE = Accessors.getFieldAccessorOrNull(CraftBossBar.class, "handle", bossEventClass);
if (BOSS_BAR_HANDLE == null) {
throw new FieldAccessException("Unable to find CraftBossBar.handle field of type " + bossEventClass.getName());
}
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public WrappedClientboundBossEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBossEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
public static WrappedClientboundBossEventPacket createAddPacket(BossBar bossBar) {
Object event = getBossEvent(bossBar);
return new WrappedClientboundBossEventPacket(PacketContainer.fromPacket(
CREATE_ADD_PACKET.invoke(null, event)));
}
public static WrappedClientboundBossEventPacket createRemovePacket(UUID id) {
return new WrappedClientboundBossEventPacket(PacketContainer.fromPacket(
CREATE_REMOVE_PACKET.invoke(null, id)));
}
public static WrappedClientboundBossEventPacket createUpdateProgressPacket(BossBar bossBar) {
Object event = getBossEvent(bossBar);
return new WrappedClientboundBossEventPacket(PacketContainer.fromPacket(
CREATE_UPDATE_PROGRESS_PACKET.invoke(null, event)));
}
public static WrappedClientboundBossEventPacket createUpdateNamePacket(BossBar bossBar) {
Object event = getBossEvent(bossBar);
return new WrappedClientboundBossEventPacket(PacketContainer.fromPacket(
CREATE_UPDATE_NAME_PACKET.invoke(null, event)));
}
public static WrappedClientboundBossEventPacket createUpdateStylePacket(BossBar bossBar) {
Object event = getBossEvent(bossBar);
return new WrappedClientboundBossEventPacket(PacketContainer.fromPacket(
CREATE_UPDATE_STYLE_PACKET.invoke(null, event)));
}
public static WrappedClientboundBossEventPacket createUpdatePropertiesPacket(BossBar bossBar) {
Object event = getBossEvent(bossBar);
return new WrappedClientboundBossEventPacket(PacketContainer.fromPacket(
CREATE_UPDATE_PROPERTIES_PACKET.invoke(null, event)));
}
private static Object getBossEvent(BossBar bossBar) {
Objects.requireNonNull(bossBar, "bossBar");
if (!(bossBar instanceof CraftBossBar)) {
throw new IllegalArgumentException("BossBar must be a CraftBossBar: " + bossBar.getClass().getName());
}
return BOSS_BAR_HANDLE.get(bossBar);
}
public UUID getId() {
return handle.getUUIDs().read(0);
}
public void setId(UUID id) {
handle.getUUIDs().write(0, id);
}
/**
* Returns the raw NMS {@code BossEvent$Operation} object describing what this packet
* does (add / remove / update progress / update name / update style / update properties).
*
* @apiNote This is an escape hatch that exposes the unwrapped NMS handle. Prefer the
* typed {@code createXxxPacket(BossBar)} factories above when constructing a packet;
* this accessor is provided only for inspecting packets received via
* {@link com.comphenix.protocol.events.PacketAdapter}.
*/
public Object getOperation() {
return handle.getModifier().read(1);
}
/**
* Replaces the raw NMS {@code BossEvent$Operation} object on this packet.
*
* @apiNote This is an escape hatch — callers must construct an NMS-internal
* {@code BossEvent$Operation} subtype (e.g. {@code AddOperation}, {@code UpdateProgressOperation}).
* Prefer the typed {@code createXxxPacket(BossBar)} factories above for normal use.
*/
public void setOperation(Object operation) {
handle.getModifier().write(1, operation);
}
}
@@ -0,0 +1,47 @@
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.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Converters;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.ArrayList;
/**
* Wrapper for {@code ClientboundBundlePacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Iterable<Packet<?>> subPackets} - bundled packets sent together</li>
* </ul>
*/
public class WrappedClientboundBundlePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.BUNDLE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(Iterable.class, Converters.iterable(
BukkitConverters.getPacketContainerConverter(), ArrayList::new, ArrayList::new));
public WrappedClientboundBundlePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundBundlePacket(Iterable<PacketContainer> subPackets) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(subPackets)));
}
public WrappedClientboundBundlePacket(PacketContainer packet) {
super(packet, TYPE);
}
public Iterable<PacketContainer> getSubPackets() {
return handle.getPacketBundles().read(0);
}
public void setSubPackets(Iterable<PacketContainer> subPackets) {
handle.getPacketBundles().write(0, subPackets);
}
}
@@ -0,0 +1,52 @@
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.wrappers.EnumWrappers;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundChangeDifficultyPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code Difficulty difficulty} – the current difficulty level</li>
* <li>{@code boolean locked} – whether the difficulty is locked by the server</li>
* </ul>
*/
public class WrappedClientboundChangeDifficultyPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.SERVER_DIFFICULTY;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(EnumWrappers.getDifficultyClass(), EnumWrappers.getDifficultyConverter())
.withParam(boolean.class);
public WrappedClientboundChangeDifficultyPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundChangeDifficultyPacket(EnumWrappers.Difficulty difficulty, boolean locked) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(difficulty, locked)));
}
public WrappedClientboundChangeDifficultyPacket(PacketContainer packet) {
super(packet, TYPE);
}
public EnumWrappers.Difficulty getDifficulty() {
return handle.getDifficulties().read(0);
}
public void setDifficulty(EnumWrappers.Difficulty difficulty) {
handle.getDifficulties().write(0, difficulty);
}
public boolean isLocked() {
return handle.getBooleans().read(0);
}
public void setLocked(boolean locked) {
handle.getBooleans().write(0, locked);
}
}
@@ -0,0 +1,41 @@
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 net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundChunkBatchFinishedPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int batchSize} – number of chunks in the completed batch</li>
* </ul>
*/
public class WrappedClientboundChunkBatchFinishedPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.CHUNK_BATCH_FINISHED;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class);
public WrappedClientboundChunkBatchFinishedPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundChunkBatchFinishedPacket(int batchSize) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(batchSize)));
}
public WrappedClientboundChunkBatchFinishedPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getBatchSize() {
return handle.getIntegers().read(0);
}
public void setBatchSize(int batchSize) {
handle.getIntegers().write(0, batchSize);
}
}
@@ -0,0 +1,41 @@
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.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedChunkBiomeData;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.List;
/**
* Wrapper for {@code ClientboundChunksBiomesPacket} (game phase, clientbound).
*/
public class WrappedClientboundChunksBiomesPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.CHUNKS_BIOMES;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(List.class, BukkitConverters.getListConverter(WrappedChunkBiomeData.CONVERTER));
public WrappedClientboundChunksBiomesPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundChunksBiomesPacket(List<WrappedChunkBiomeData> chunkBiomeData) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(chunkBiomeData)));
}
public WrappedClientboundChunksBiomesPacket(PacketContainer packet) {
super(packet, TYPE);
}
public List<WrappedChunkBiomeData> getChunkBiomeData() {
return handle.getLists(WrappedChunkBiomeData.CONVERTER).read(0);
}
public void setChunkBiomeData(List<WrappedChunkBiomeData> chunkBiomeData) {
handle.getLists(WrappedChunkBiomeData.CONVERTER).write(0, chunkBiomeData);
}
}
@@ -0,0 +1,41 @@
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 net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundClearTitlesPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code boolean resetTimes} – if {@code true}, also resets fade-in/stay/fade-out times</li>
* </ul>
*/
public class WrappedClientboundClearTitlesPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.CLEAR_TITLES;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(boolean.class);
public WrappedClientboundClearTitlesPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundClearTitlesPacket(boolean resetTimes) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(resetTimes)));
}
public WrappedClientboundClearTitlesPacket(PacketContainer packet) {
super(packet, TYPE);
}
public boolean isResetTimes() {
return handle.getBooleans().read(0);
}
public void setResetTimes(boolean resetTimes) {
handle.getBooleans().write(0, resetTimes);
}
}
@@ -0,0 +1,52 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCommandSuggestionsPacket} (game phase, clientbound).
*/
public class WrappedClientboundCommandSuggestionsPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.TAB_COMPLETE;
public WrappedClientboundCommandSuggestionsPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCommandSuggestionsPacket(int id, int start, int length) {
this();
setId(id);
setStart(start);
setLength(length);
}
public WrappedClientboundCommandSuggestionsPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getId() {
return handle.getIntegers().read(0);
}
public void setId(int id) {
handle.getIntegers().write(0, id);
}
public int getStart() {
return handle.getIntegers().read(1);
}
public void setStart(int start) {
handle.getIntegers().write(1, start);
}
public int getLength() {
return handle.getIntegers().read(2);
}
public void setLength(int length) {
handle.getIntegers().write(2, length);
}
}
@@ -0,0 +1,25 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCommandsPacket} (game phase, clientbound).
*/
public class WrappedClientboundCommandsPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.COMMANDS;
public WrappedClientboundCommandsPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCommandsPacket(PacketContainer packet) {
super(packet, TYPE);
}
// TODO: missing field — Brigadier command tree (RootCommandNode<SharedSuggestionProvider>)
// No ProtocolLib accessor exists for the serialised command tree.
// Use handle.getModifier().read(0) to access the raw RootCommandNode object.
}
@@ -0,0 +1,43 @@
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 net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundContainerClosePacket} (Play phase, clientbound).
*
* <p>Instructs the client to close the given container window.
*
* <p>Packet structure:
* <ul>
* <li>{@code int windowId} – ID of the window to close (0 = player inventory)</li>
* </ul>
*/
public class WrappedClientboundContainerClosePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.CLOSE_WINDOW;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class);
public WrappedClientboundContainerClosePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundContainerClosePacket(int windowId) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(windowId)));
}
public WrappedClientboundContainerClosePacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getWindowId() {
return handle.getIntegers().read(0);
}
public void setWindowId(int windowId) {
handle.getIntegers().write(0, windowId);
}
}
@@ -0,0 +1,73 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import java.util.List;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.inventory.ItemStack;
/**
* Wrapper for {@code ClientboundContainerSetContentPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int containerId} – ID of the container whose contents are being set</li>
* <li>{@code int stateId} – state ID used for sync with the server</li>
* <li>{@code List<ItemStack> items} – full slot contents</li>
* <li>{@code ItemStack carriedItem} – item currently held on the cursor</li>
* </ul>
*/
public class WrappedClientboundContainerSetContentPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.WINDOW_ITEMS;
public WrappedClientboundContainerSetContentPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundContainerSetContentPacket(int containerId, int stateId, List<ItemStack> items, ItemStack carriedItem) {
this();
setContainerId(containerId);
setStateId(stateId);
setItems(items);
setCarriedItem(carriedItem);
}
public WrappedClientboundContainerSetContentPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getContainerId() {
return handle.getIntegers().read(0);
}
public void setContainerId(int containerId) {
handle.getIntegers().write(0, containerId);
}
public int getStateId() {
return handle.getIntegers().read(1);
}
public void setStateId(int stateId) {
handle.getIntegers().write(1, stateId);
}
public List<ItemStack> getItems() {
return handle.getItemListModifier().read(0);
}
public void setItems(List<ItemStack> items) {
handle.getItemListModifier().write(0, items);
}
public ItemStack getCarriedItem() {
return handle.getItemModifier().read(0);
}
public void setCarriedItem(ItemStack carriedItem) {
handle.getItemModifier().write(0, carriedItem);
}
}
@@ -0,0 +1,61 @@
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 net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundContainerSetDataPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int windowId} – container window ID</li>
* <li>{@code int property} – property index (container-type dependent)</li>
* <li>{@code int value} – new value of the property</li>
* </ul>
*/
public class WrappedClientboundContainerSetDataPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.WINDOW_DATA;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(int.class)
.withParam(int.class);
public WrappedClientboundContainerSetDataPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundContainerSetDataPacket(int windowId, int property, int value) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(windowId, property, value)));
}
public WrappedClientboundContainerSetDataPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getWindowId() {
return handle.getIntegers().read(0);
}
public void setWindowId(int windowId) {
handle.getIntegers().write(0, windowId);
}
public int getProperty() {
return handle.getIntegers().read(1);
}
public void setProperty(int property) {
handle.getIntegers().write(1, property);
}
public int getValue() {
return handle.getIntegers().read(2);
}
public void setValue(int value) {
handle.getIntegers().write(2, value);
}
}
@@ -0,0 +1,75 @@
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 net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.inventory.ItemStack;
/**
* Wrapper for {@code ClientboundContainerSetSlotPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int windowId} – container window ID; 0 = player inventory, -1 = cursor slot,
* -2 = all slots</li>
* <li>{@code int stateId} – container state ID for synchronisation</li>
* <li>{@code int slot} – slot index within the container</li>
* <li>{@code ItemStack item} – item to place in the slot</li>
* </ul>
*/
public class WrappedClientboundContainerSetSlotPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.SET_SLOT;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(int.class)
.withParam(int.class)
.withParam(MinecraftReflection.getItemStackClass(), BukkitConverters.getItemStackConverter());
public WrappedClientboundContainerSetSlotPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundContainerSetSlotPacket(int windowId, int stateId, int slot, ItemStack item) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(windowId, stateId, slot, item)));
}
public WrappedClientboundContainerSetSlotPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getWindowId() {
return handle.getIntegers().read(0);
}
public void setWindowId(int windowId) {
handle.getIntegers().write(0, windowId);
}
public int getStateId() {
return handle.getIntegers().read(1);
}
public void setStateId(int stateId) {
handle.getIntegers().write(1, stateId);
}
public int getSlot() {
return handle.getIntegers().read(2);
}
public void setSlot(int slot) {
handle.getIntegers().write(2, slot);
}
public ItemStack getItem() {
return handle.getItemModifier().read(0);
}
public void setItem(ItemStack item) {
handle.getItemModifier().write(0, item);
}
}
@@ -0,0 +1,43 @@
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.MinecraftKey;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCookieRequestPacket} (game 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.Play.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);
}
}
@@ -0,0 +1,53 @@
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.MinecraftKey;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCooldownPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code MinecraftKey item} – namespaced item key the cooldown applies to</li>
* <li>{@code int ticks} – cooldown duration in ticks; 0 to clear the cooldown</li>
* </ul>
*/
public class WrappedClientboundCooldownPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.SET_COOLDOWN;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getMinecraftKeyClass(), MinecraftKey.getConverter())
.withParam(int.class);
public WrappedClientboundCooldownPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCooldownPacket(MinecraftKey item, int ticks) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(item, ticks)));
}
public WrappedClientboundCooldownPacket(PacketContainer packet) {
super(packet, TYPE);
}
public MinecraftKey getItem() {
return handle.getMinecraftKeys().read(0);
}
public void setItem(MinecraftKey item) {
handle.getMinecraftKeys().write(0, item);
}
public int getTicks() {
return handle.getIntegers().read(0);
}
public void setTicks(int ticks) {
handle.getIntegers().write(0, ticks);
}
}
Loaded 100 of 556 files, more files were not shown because too many files have changed in this diff. Show more