Compare commits

...
15 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
507 changed files with 34419 additions and 30 deletions

No files matched your search

+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)));
}
}
```
@@ -66,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;
@@ -419,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>
@@ -803,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.
@@ -818,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.
@@ -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);
}
}
@@ -928,6 +928,10 @@ public final class MinecraftReflection {
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", "sounds.SoundEvents");
}
@@ -0,0 +1,3 @@
package com.comphenix.protocol.utility;
public record Tuple<A, B>(A first, B second) {}
@@ -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
@@ -1436,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);
}
@@ -724,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;
@@ -752,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;
@@ -874,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());
@@ -901,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());
@@ -1090,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);
@@ -1199,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,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 + "}";
}
}
@@ -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
+ '}';
}
}
@@ -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 + '}';
}
}
@@ -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 + "}";
}
}
@@ -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);
}
}
@@ -0,0 +1,64 @@
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.Converters;
import com.comphenix.protocol.wrappers.EnumWrappers;
import java.util.Arrays;
import java.util.List;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCustomChatCompletionsPacket} (game phase, clientbound).
*
* <p>NMS field order: {@code action (global 0), entries (global 1)}
*/
public class WrappedClientboundCustomChatCompletionsPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.CUSTOM_CHAT_COMPLETIONS;
/**
* Mirrors {@code ClientboundCustomChatCompletionsPacket.Action}.
* Global field index 0 — constants must match NMS names exactly.
*/
public enum Action { ADD, REMOVE, SET }
private static final Class<?> ACTION_NMS_CLASS = Arrays.stream(TYPE.getPacketClass().getDeclaredClasses())
.filter(Class::isEnum)
.findFirst()
.orElse(null);
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(ACTION_NMS_CLASS, new EnumWrappers.EnumConverter<>(ACTION_NMS_CLASS, Action.class))
.withParam(List.class);
public WrappedClientboundCustomChatCompletionsPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundCustomChatCompletionsPacket(Action action, List<String> entries) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(action, entries)));
}
public WrappedClientboundCustomChatCompletionsPacket(PacketContainer packet) {
super(packet, TYPE);
}
/** Returns the completion action (ADD, REMOVE, or SET). Global field index 0. */
public Action getAction() {
return handle.getEnumModifier(Action.class, 0).readSafely(0);
}
public void setAction(Action action) {
handle.getEnumModifier(Action.class, 0).writeSafely(0, action);
}
public List<String> getEntries() {
return handle.getLists(Converters.passthrough(String.class)).readSafely(0);
}
public void setEntries(List<String> entries) {
handle.getLists(Converters.passthrough(String.class)).writeSafely(0, entries);
}
}
@@ -0,0 +1,42 @@
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.CustomPacketPayloadWrapper;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundCustomPayloadPacket} (game 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.Play.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.game.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} (game 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.Play.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,94 @@
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 com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.damage.DamageType;
import org.bukkit.util.Vector;
import java.util.Optional;
/**
* Wrapper for {@code ClientboundDamageEventPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int entityId} – entity that received the damage</li>
* <li>{@code Holder<DamageType> sourceType} – damage type</li>
* <li>{@code int sourceCauseId} – entity ID of the damage cause, or {@code -1} if absent</li>
* <li>{@code int sourceDirectId} – entity ID of the direct source, or {@code -1} if absent</li>
* <li>{@code Optional<Vector> sourcePosition} – world position of the damage source, if any</li>
* </ul>
*/
public class WrappedClientboundDamageEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DAMAGE_EVENT;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(MinecraftReflection.getHolderClass(),
Converters.holder(BukkitConverters.getDamageTypeConverter(),
WrappedRegistry.getRegistry(MinecraftReflection.getDamageTypeClass())))
.withParam(int.class)
.withParam(int.class)
.withParam(Optional.class, Converters.optional(BukkitConverters.getVectorConverter()));
public WrappedClientboundDamageEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDamageEventPacket(int entityId, DamageType sourceType, int sourceCauseId, int sourceDirectId, Optional<Vector> sourcePosition) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(entityId, sourceType, sourceCauseId, sourceDirectId, sourcePosition)));
}
public WrappedClientboundDamageEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getEntityId() {
return handle.getIntegers().readSafely(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().writeSafely(0, entityId);
}
/** Returns the damage type, unwrapped from its registry {@code Holder}. */
public DamageType getSourceType() {
return handle.getDamageTypes().readSafely(0);
}
public void setSourceType(DamageType sourceType) {
handle.getDamageTypes().writeSafely(0, sourceType);
}
public int getSourceCauseId() {
return handle.getIntegers().readSafely(1);
}
public void setSourceCauseId(int sourceCauseId) {
handle.getIntegers().writeSafely(1, sourceCauseId);
}
public int getSourceDirectId() {
return handle.getIntegers().readSafely(2);
}
public void setSourceDirectId(int sourceDirectId) {
handle.getIntegers().writeSafely(2, sourceDirectId);
}
public Optional<Vector> getSourcePosition() {
return handle.getOptionals(BukkitConverters.getVectorConverter()).readSafely(0);
}
public void setSourcePosition(Optional<Vector> sourcePosition) {
handle.getOptionals(BukkitConverters.getVectorConverter()).writeSafely(0, sourcePosition);
}
}
@@ -0,0 +1,36 @@
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 net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundDebugBlockValuePacket} (game phase, clientbound).
*
* <p>Fields:
* <ul>
* <li>{@code BlockPos blockPos} – the block position associated with this debug value</li>
* <li>{@code DebugSubscription.Update<?> update} – the debug update data (opaque, no ProtocolLib accessor)</li>
* </ul>
*/
public class WrappedClientboundDebugBlockValuePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DEBUG_BLOCK_VALUE;
public WrappedClientboundDebugBlockValuePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDebugBlockValuePacket(PacketContainer packet) {
super(packet, TYPE);
}
public BlockPosition getBlockPos() {
return handle.getBlockPositionModifier().read(0);
}
public void setBlockPos(BlockPosition blockPos) {
handle.getBlockPositionModifier().write(0, blockPos);
}
}
@@ -0,0 +1,36 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundDebugChunkValuePacket} (game phase, clientbound).
*
* <p>Fields:
* <ul>
* <li>{@code ChunkPos chunkPos} – the chunk position associated with this debug value</li>
* <li>{@code DebugSubscription.Update<?> update} – the debug update data (opaque, no ProtocolLib accessor)</li>
* </ul>
*/
public class WrappedClientboundDebugChunkValuePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DEBUG_CHUNK_VALUE;
public WrappedClientboundDebugChunkValuePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDebugChunkValuePacket(PacketContainer packet) {
super(packet, TYPE);
}
public ChunkCoordIntPair getChunkPos() {
return handle.getChunkCoordIntPairs().read(0);
}
public void setChunkPos(ChunkCoordIntPair chunkPos) {
handle.getChunkCoordIntPairs().write(0, chunkPos);
}
}
@@ -0,0 +1,35 @@
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 ClientboundDebugEntityValuePacket} (game phase, clientbound).
*
* <p>Fields:
* <ul>
* <li>{@code int entityId} – the entity ID</li>
* <li>{@code DebugSubscription.Update<?> update} – the debug update data (opaque, no ProtocolLib accessor)</li>
* </ul>
*/
public class WrappedClientboundDebugEntityValuePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DEBUG_ENTITY_VALUE;
public WrappedClientboundDebugEntityValuePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDebugEntityValuePacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getEntityId() {
return handle.getIntegers().read(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().write(0, entityId);
}
}
@@ -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 ClientboundDebugEventPacket} (game phase, clientbound).
* Sends a debug event. The event field has no ProtocolLib accessor.
*/
public class WrappedClientboundDebugEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DEBUG_EVENT;
public WrappedClientboundDebugEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDebugEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
// TODO: missing field 'event' (NMS type: DebugSubscription.Event<?> — registry-backed debug event)
// No ProtocolLib accessor exists. Use handle.getModifier().read(0) for the raw event object.
}
@@ -0,0 +1,66 @@
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.EnumWrappers;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundDebugSamplePacket} (game phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code long[] sample} – array of debug timing samples</li>
* <li>{@code DebugSampleType debugSampleType} – the type of debug data being sampled</li>
* </ul>
*/
public class WrappedClientboundDebugSamplePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DEBUG_SAMPLE;
/**
* The type of debug sample data being sent.
* Matches {@code net.minecraft.util.debugchart.RemoteDebugSampleType}.
*/
public enum DebugSampleType {
TICK_TIME
}
private static final Class<?> REMOTE_DEBUG_SAMPLE_TYPE_CLASS =
MinecraftReflection.getMinecraftClass("util.debugchart.RemoteDebugSampleType");
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(long[].class)
.withParam(REMOTE_DEBUG_SAMPLE_TYPE_CLASS,
new EnumWrappers.EnumConverter<>(REMOTE_DEBUG_SAMPLE_TYPE_CLASS, DebugSampleType.class));
public WrappedClientboundDebugSamplePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDebugSamplePacket(long[] sample, DebugSampleType debugSampleType) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(sample, debugSampleType)));
}
public WrappedClientboundDebugSamplePacket(PacketContainer packet) {
super(packet, TYPE);
}
public long[] getSample() {
return handle.getSpecificModifier(long[].class).readSafely(0);
}
public void setSample(long[] sample) {
handle.getSpecificModifier(long[].class).writeSafely(0, sample);
}
public DebugSampleType getDebugSampleType() {
return handle.getEnumModifier(DebugSampleType.class, 1).readSafely(0);
}
public void setDebugSampleType(DebugSampleType debugSampleType) {
handle.getEnumModifier(DebugSampleType.class, 1).writeSafely(0, debugSampleType);
}
}
@@ -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.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedMessageSignature;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundDeleteChatPacket} (game phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code MessageSignature.Packed messageSignature} – identifies the message to delete</li>
* </ul>
*
* <p>Note: The NMS field is {@code MessageSignature.Packed} (an inner record), which differs from
* the plain {@code MessageSignature} class used by {@code getMessageSignatures()}. As a result,
* the {@code messageSignature} field is not accessible via the standard modifier; getter/setter
* operate as no-ops and always return {@code null}.
*/
public class WrappedClientboundDeleteChatPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DELETE_CHAT_MESSAGE;
public WrappedClientboundDeleteChatPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDeleteChatPacket(WrappedMessageSignature messageSignature) {
this();
setMessageSignature(messageSignature);
}
public WrappedClientboundDeleteChatPacket(PacketContainer packet) {
super(packet, TYPE);
}
public WrappedMessageSignature getMessageSignature() {
return handle.getMessageSignatures().readSafely(0);
}
public void setMessageSignature(WrappedMessageSignature messageSignature) {
handle.getMessageSignatures().writeSafely(0, messageSignature);
}
}
@@ -0,0 +1,44 @@
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 com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundDisconnectPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code WrappedChatComponent reason} – disconnect reason shown to the player</li>
* </ul>
*/
public class WrappedClientboundDisconnectPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.KICK_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(PacketContainer.fromPacket(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,62 @@
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 com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedChatTypeBound;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.Optional;
/**
* Wrapper for {@code ClientboundDisguisedChatPacket} (game phase, clientbound).
*/
public class WrappedClientboundDisguisedChatPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.DISGUISED_CHAT;
private static final Class<?> CHAT_TYPE_BOUND_CLASS =
MinecraftReflection.getMinecraftClass("network.chat.ChatType$Bound");
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter())
.withParam(CHAT_TYPE_BOUND_CLASS, WrappedChatTypeBound.CONVERTER);
public WrappedClientboundDisguisedChatPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundDisguisedChatPacket(WrappedChatComponent message) {
this(message, new WrappedChatTypeBound(
new com.comphenix.protocol.wrappers.MinecraftKey("chat"),
WrappedChatComponent.fromText(""),
Optional.empty()));
}
public WrappedClientboundDisguisedChatPacket(WrappedChatComponent message, WrappedChatTypeBound chatType) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(message, chatType)));
}
public WrappedClientboundDisguisedChatPacket(PacketContainer packet) {
super(packet, TYPE);
}
public WrappedChatComponent getMessage() {
return handle.getChatComponents().read(0);
}
public void setMessage(WrappedChatComponent message) {
handle.getChatComponents().write(0, message);
}
public WrappedChatTypeBound getChatType() {
return handle.getModifier().withType(CHAT_TYPE_BOUND_CLASS, WrappedChatTypeBound.CONVERTER).read(0);
}
public void setChatType(WrappedChatTypeBound chatType) {
handle.getModifier().withType(CHAT_TYPE_BOUND_CLASS, WrappedChatTypeBound.CONVERTER).write(0, chatType);
}
}
@@ -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.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 ClientboundEntityEventPacket} (Play phase, clientbound).
*
* <p>Triggers a status/animation on an entity (e.g. hurt flash, death
* animation, taming success/failure).
*
* <p>Packet structure:
* <ul>
* <li>{@code int entityId} – the affected entity's ID</li>
* <li>{@code byte status} – event/status code (see Minecraft protocol wiki)</li>
* </ul>
*/
public class WrappedClientboundEntityEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.ENTITY_STATUS;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getEntityClass(), BukkitUnwrapper.getInstance())
.withParam(byte.class);
public WrappedClientboundEntityEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundEntityEventPacket(int entityId, byte status) {
this();
setEntityId(entityId);
setStatus(status);
}
public WrappedClientboundEntityEventPacket(Entity entity, byte status) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(entity, status)));
}
public WrappedClientboundEntityEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getEntityId() {
return handle.getIntegers().readSafely(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().writeSafely(0, entityId);
}
public Entity getEntity(World world) {
return handle.getEntityModifier(world).readSafely(0);
}
public void setEntity(Entity entity) {
handle.getEntityModifier(entity.getWorld()).writeSafely(0, entity);
}
public byte getStatus() {
return handle.getBytes().readSafely(0);
}
public void setStatus(byte status) {
handle.getBytes().writeSafely(0, status);
}
}
@@ -0,0 +1,57 @@
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.WrappedPositionMoveRotation;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundEntityPositionSyncPacket} (game phase, clientbound).
*/
public class WrappedClientboundEntityPositionSyncPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.ENTITY_POSITION_SYNC;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(MinecraftReflection.getMinecraftClass("world.entity.PositionMoveRotation"),
WrappedPositionMoveRotation.getConverter())
.withParam(boolean.class);
public WrappedClientboundEntityPositionSyncPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundEntityPositionSyncPacket(int id, WrappedPositionMoveRotation values, boolean onGround) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(id, values, onGround)));
}
public WrappedClientboundEntityPositionSyncPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getId() {
return handle.getIntegers().read(0);
}
public void setId(int id) {
handle.getIntegers().write(0, id);
}
public WrappedPositionMoveRotation getValues() {
return handle.getPositionMoveRotations().read(0);
}
public void setValues(WrappedPositionMoveRotation values) {
handle.getPositionMoveRotations().write(0, values);
}
public boolean isOnGround() {
return handle.getBooleans().read(0);
}
public void setOnGround(boolean onGround) {
handle.getBooleans().write(0, onGround);
}
}
@@ -0,0 +1,123 @@
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 com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedExplosionParticleInfo;
import com.comphenix.protocol.wrappers.WrappedParticle;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.WrappedWeightedList;
import java.util.Optional;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.Sound;
import org.bukkit.util.Vector;
/**
* Wrapper for {@code ClientboundExplodePacket} (game phase, clientbound).
* <p>
* NMS record fields (declaration order):
* <ol>
* <li>{@code Vec3 center}</li>
* <li>{@code float radius}</li>
* <li>{@code int blockCount}</li>
* <li>{@code Optional<Vec3> playerKnockback}</li>
* <li>{@code ParticleOptions explosionParticle}</li>
* <li>{@code Holder<SoundEvent> explosionSound}</li>
* <li>{@code WeightedList<ExplosionParticleInfo> blockParticles}</li>
* </ol>
*/
public class WrappedClientboundExplodePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.EXPLOSION;
public WrappedClientboundExplodePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundExplodePacket(Vector center, float radius, int blockCount, Optional<Vector> playerKnockback, WrappedParticle<?> explosionParticle, Sound explosionSound, WrappedWeightedList<WrappedExplosionParticleInfo> blockParticles) {
this();
setCenter(center);
setRadius(radius);
setBlockCount(blockCount);
setPlayerKnockback(playerKnockback);
setExplosionParticle(explosionParticle);
setExplosionSound(explosionSound);
setBlockParticles(blockParticles);
}
public WrappedClientboundExplodePacket(PacketContainer packet) {
super(packet, TYPE);
}
// ── center (Vec3) ────────────────────────────────────────────────────
public Vector getCenter() {
return handle.getVectors().read(0);
}
public void setCenter(Vector center) {
handle.getVectors().write(0, center);
}
// ── radius (float) ──────────────────────────────────────────────────
public float getRadius() {
return handle.getFloat().read(0);
}
public void setRadius(float radius) {
handle.getFloat().write(0, radius);
}
// ── blockCount (int) ────────────────────────────────────────────────
public int getBlockCount() {
return handle.getIntegers().read(0);
}
public void setBlockCount(int blockCount) {
handle.getIntegers().write(0, blockCount);
}
// ── playerKnockback (Optional<Vec3>) ────────────────────────────────
public Optional<Vector> getPlayerKnockback() {
return handle.getOptionals(BukkitConverters.getVectorConverter()).read(0);
}
public void setPlayerKnockback(Optional<Vector> playerKnockback) {
handle.getOptionals(BukkitConverters.getVectorConverter()).write(0, playerKnockback);
}
// ── explosionParticle (ParticleOptions) ─────────────────────────────
public WrappedParticle<?> getExplosionParticle() {
return handle.getNewParticles().read(0);
}
public void setExplosionParticle(WrappedParticle<?> explosionParticle) {
handle.getNewParticles().write(0, explosionParticle);
}
// ── explosionSound (Holder<SoundEvent>) ─────────────────────────────
public Sound getExplosionSound() {
return handle.getSoundEffects().read(0);
}
public void setExplosionSound(Sound explosionSound) {
handle.getSoundEffects().write(0, explosionSound);
}
// ── blockParticles (WeightedList<ExplosionParticleInfo>) ────────────
public WrappedWeightedList<WrappedExplosionParticleInfo> getBlockParticles() {
return handle.getWeightedLists(WrappedExplosionParticleInfo.getConverter()).read(0);
}
public void setBlockParticles(WrappedWeightedList<WrappedExplosionParticleInfo> blockParticles) {
handle.getWeightedLists(WrappedExplosionParticleInfo.getConverter()).write(0, blockParticles);
}
}
@@ -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 com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundForgetLevelChunkPacket} (Play phase, clientbound).
*
* <p>Tells the client to unload the specified chunk column.
*
* <p>Packet structure:
* <ul>
* <li>{@code ChunkPos pos} – chunk coordinates of the column to unload</li>
* </ul>
*/
public class WrappedClientboundForgetLevelChunkPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.UNLOAD_CHUNK;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getChunkCoordIntPair(), ChunkCoordIntPair.getConverter());
public WrappedClientboundForgetLevelChunkPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundForgetLevelChunkPacket(ChunkCoordIntPair pos) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(pos)));
}
public WrappedClientboundForgetLevelChunkPacket(PacketContainer packet) {
super(packet, TYPE);
}
public ChunkCoordIntPair getPos() {
return handle.getChunkCoordIntPairs().read(0);
}
public void setPos(ChunkCoordIntPair pos) {
handle.getChunkCoordIntPairs().write(0, pos);
}
public int getChunkX() {
return getPos().getChunkX();
}
public void setChunkX(int x) {
setPos(new ChunkCoordIntPair(x, getChunkZ()));
}
public int getChunkZ() {
return getPos().getChunkZ();
}
public void setChunkZ(int z) {
setPos(new ChunkCoordIntPair(getChunkX(), z));
}
}
@@ -0,0 +1,59 @@
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;
/**
* Wrapper for {@code ClientboundGameEventPacket} (Play phase, clientbound).
*
* <p>Notifies the client of a game-state change such as starting rain, changing
* game mode, or ending the game.
*
* <p>Packet structure:
* <ul>
* <li>{@code int event} – the game-event/reason ID (see Minecraft protocol wiki)</li>
* <li>{@code float value} – numeric parameter for the event</li>
* </ul>
*/
public class WrappedClientboundGameEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.GAME_STATE_CHANGE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getGameStateClass(), BukkitConverters.getGameStateConverter())
.withParam(float.class);
public WrappedClientboundGameEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundGameEventPacket(int event, float value) {
this(new PacketContainer(TYPE, CONSTRUCTOR.create(event, value)));
}
public WrappedClientboundGameEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
/** Returns the game-event ID (reason code). */
public int getEvent() {
return handle.getGameStateIDs().readSafely(0);
}
public void setEvent(int event) {
handle.getGameStateIDs().writeSafely(0, event);
}
/** Returns the float parameter associated with the event. */
public float getValue() {
return handle.getFloat().readSafely(0);
}
public void setValue(float value) {
handle.getFloat().writeSafely(0, value);
}
}
@@ -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.reflect.EquivalentConverter;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import net.dmulloy2.protocol.AbstractPacket;
import java.util.Map;
/**
* Wrapper for {@code ClientboundGameRuleValuesPacket} (game phase, clientbound).
*
* <p>Fields:
* <ul>
* <li>{@code Map<ResourceKey<GameRule<?>>, String> values} – current game-rule values by key</li>
* </ul>
*/
public class WrappedClientboundGameRuleValuesPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.GAME_RULE_VALUES;
/** Converter between NMS {@code ResourceKey<GameRule<?>>} and {@link MinecraftKey}. */
private static final EquivalentConverter<MinecraftKey> GAME_RULE_KEY_CONVERTER =
WrappedRegistry.getGameRuleRegistry().resourceKeyConverter();
public WrappedClientboundGameRuleValuesPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundGameRuleValuesPacket(PacketContainer packet) {
super(packet, TYPE);
}
/**
* Returns the game-rule values as a {@code Map<MinecraftKey, String>}.
* Each key is the game rule's identifier (e.g. {@code minecraft:doFireTick}).
*/
public Map<MinecraftKey, String> getValues() {
Map<MinecraftKey, String> values = handle.getMaps(GAME_RULE_KEY_CONVERTER, Converters.passthrough(String.class)).read(0);
return values != null ? values : Map.of();
}
/**
* Sets the game-rule values from a {@code Map<MinecraftKey, String>}.
* Each key is converted to a {@code ResourceKey<GameRule<?>>} in the {@code game_rule} registry.
*/
public void setValues(Map<MinecraftKey, String> values) {
handle.getMaps(GAME_RULE_KEY_CONVERTER, Converters.passthrough(String.class)).write(0, values);
}
}
@@ -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.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundGameTestHighlightPosPacket} (game phase, clientbound).
*/
public class WrappedClientboundGameTestHighlightPosPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.GAME_TEST_HIGHLIGHT_POS;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter())
.withParam(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter());
public WrappedClientboundGameTestHighlightPosPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundGameTestHighlightPosPacket(BlockPosition absolutePos, BlockPosition relativePos) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(absolutePos, relativePos)));
}
public WrappedClientboundGameTestHighlightPosPacket(PacketContainer packet) {
super(packet, TYPE);
}
public BlockPosition getAbsolutePos() {
return handle.getBlockPositionModifier().read(0);
}
public void setAbsolutePos(BlockPosition absolutePos) {
handle.getBlockPositionModifier().write(0, absolutePos);
}
public BlockPosition getRelativePos() {
return handle.getBlockPositionModifier().read(1);
}
public void setRelativePos(BlockPosition relativePos) {
handle.getBlockPositionModifier().write(1, relativePos);
}
}
@@ -0,0 +1,69 @@
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;
import org.bukkit.World;
import org.bukkit.entity.Entity;
/**
* Wrapper for {@code ClientboundHurtAnimationPacket} (Play phase, clientbound).
*
* <p>Triggers the hurt/damage animation on an entity. The {@code yaw} field
* controls the direction the hurt animation originates from, in degrees.
*
* <p>Packet structure:
* <ul>
* <li>{@code int id} – entity ID</li>
* <li>{@code float yaw} – yaw angle of the damage source (degrees)</li>
* </ul>
*/
public class WrappedClientboundHurtAnimationPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.HURT_ANIMATION;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(float.class);
public WrappedClientboundHurtAnimationPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundHurtAnimationPacket(int entityId, float yaw) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(entityId, yaw)));
}
public WrappedClientboundHurtAnimationPacket(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);
}
public Entity getEntity(World world) {
return handle.getEntityModifier(world).read(0);
}
// -------------------------------------------------------------------------
// Damage source yaw
// -------------------------------------------------------------------------
/** Yaw angle (degrees) of the incoming damage direction. */
public float getYaw() {
return handle.getFloat().read(0);
}
public void setYaw(float yaw) {
handle.getFloat().write(0, yaw);
}
}
@@ -0,0 +1,109 @@
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 ClientboundInitializeBorderPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code double newCenterX}</li>
* <li>{@code double newCenterZ}</li>
* <li>{@code double oldSize}</li>
* <li>{@code double newSize}</li>
* <li>{@code long lerpTime}</li>
* <li>{@code int newAbsoluteMaxSize}</li>
* <li>{@code int warningBlocks}</li>
* <li>{@code int warningTime}</li>
* </ul>
*/
public class WrappedClientboundInitializeBorderPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.INITIALIZE_BORDER;
public WrappedClientboundInitializeBorderPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundInitializeBorderPacket(double newCenterX, double newCenterZ, double oldSize, double newSize, long lerpTime, int newAbsoluteMaxSize, int warningBlocks, int warningTime) {
this();
setNewCenterX(newCenterX);
setNewCenterZ(newCenterZ);
setOldSize(oldSize);
setNewSize(newSize);
setLerpTime(lerpTime);
setNewAbsoluteMaxSize(newAbsoluteMaxSize);
setWarningBlocks(warningBlocks);
setWarningTime(warningTime);
}
public WrappedClientboundInitializeBorderPacket(PacketContainer packet) {
super(packet, TYPE);
}
public double getNewCenterX() {
return handle.getDoubles().read(0);
}
public void setNewCenterX(double newCenterX) {
handle.getDoubles().write(0, newCenterX);
}
public double getNewCenterZ() {
return handle.getDoubles().read(1);
}
public void setNewCenterZ(double newCenterZ) {
handle.getDoubles().write(1, newCenterZ);
}
public double getOldSize() {
return handle.getDoubles().read(2);
}
public void setOldSize(double oldSize) {
handle.getDoubles().write(2, oldSize);
}
public double getNewSize() {
return handle.getDoubles().read(3);
}
public void setNewSize(double newSize) {
handle.getDoubles().write(3, newSize);
}
public long getLerpTime() {
return handle.getLongs().read(0);
}
public void setLerpTime(long lerpTime) {
handle.getLongs().write(0, lerpTime);
}
public int getNewAbsoluteMaxSize() {
return handle.getIntegers().read(0);
}
public void setNewAbsoluteMaxSize(int newAbsoluteMaxSize) {
handle.getIntegers().write(0, newAbsoluteMaxSize);
}
public int getWarningBlocks() {
return handle.getIntegers().read(1);
}
public void setWarningBlocks(int warningBlocks) {
handle.getIntegers().write(1, warningBlocks);
}
public int getWarningTime() {
return handle.getIntegers().read(2);
}
public void setWarningTime(int warningTime) {
handle.getIntegers().write(2, warningTime);
}
}
@@ -0,0 +1,44 @@
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 ClientboundKeepAlivePacket} (Play phase, clientbound).
*
* <p>Sent by the server to verify the client connection. The client must
* respond with a matching {@code ServerboundKeepAlivePacket}.
*
* <p>Packet structure:
* <ul>
* <li>{@code long id} – keep-alive token to echo back</li>
* </ul>
*/
public class WrappedClientboundKeepAlivePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.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(PacketContainer.fromPacket(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,43 @@
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 ClientboundLevelChunkWithLightPacket} (game phase, clientbound).
*/
public class WrappedClientboundLevelChunkWithLightPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.MAP_CHUNK;
public WrappedClientboundLevelChunkWithLightPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundLevelChunkWithLightPacket(int x, int z) {
this();
setX(x);
setZ(z);
}
public WrappedClientboundLevelChunkWithLightPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getX() {
return handle.getIntegers().read(0);
}
public void setX(int x) {
handle.getIntegers().write(0, x);
}
public int getZ() {
return handle.getIntegers().read(1);
}
public void setZ(int z) {
handle.getIntegers().write(1, z);
}
}
@@ -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.injector.EquivalentConstructor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundLevelEventPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int type} – event type ID</li>
* <li>{@code BlockPosition pos} – position of the event</li>
* <li>{@code int data} – event-specific data</li>
* <li>{@code boolean broadcastToAll} – if {@code true}, the event is broadcast to all nearby players</li>
* </ul>
*/
public class WrappedClientboundLevelEventPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.WORLD_EVENT;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(MinecraftReflection.getBlockPositionClass(), BlockPosition.getConverter())
.withParam(int.class)
.withParam(boolean.class);
public WrappedClientboundLevelEventPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundLevelEventPacket(int type, BlockPosition pos, int data, boolean broadcastToAll) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(type, pos, data, broadcastToAll)));
}
public WrappedClientboundLevelEventPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getType() {
return handle.getIntegers().read(0);
}
public void setType(int type) {
handle.getIntegers().write(0, type);
}
public BlockPosition getPos() {
return handle.getBlockPositionModifier().read(0);
}
public void setPos(BlockPosition pos) {
handle.getBlockPositionModifier().write(0, pos);
}
public int getData() {
return handle.getIntegers().read(1);
}
public void setData(int data) {
handle.getIntegers().write(1, data);
}
public boolean isBroadcastToAll() {
return handle.getBooleans().read(0);
}
public void setBroadcastToAll(boolean broadcastToAll) {
handle.getBooleans().write(0, broadcastToAll);
}
}
@@ -0,0 +1,125 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.WrappedParticle;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundLevelParticlesPacket} (game phase, clientbound).
*/
public class WrappedClientboundLevelParticlesPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.WORLD_PARTICLES;
public WrappedClientboundLevelParticlesPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundLevelParticlesPacket(boolean overrideLimiter, boolean alwaysShow, double x, double y, double z, float xDist, float yDist, float zDist, float maxSpeed, int count, WrappedParticle<?> particle) {
this();
setOverrideLimiter(overrideLimiter);
setAlwaysShow(alwaysShow);
setX(x);
setY(y);
setZ(z);
setXDist(xDist);
setYDist(yDist);
setZDist(zDist);
setMaxSpeed(maxSpeed);
setCount(count);
setParticle(particle);
}
public WrappedClientboundLevelParticlesPacket(PacketContainer packet) {
super(packet, TYPE);
}
public boolean isOverrideLimiter() {
return handle.getBooleans().read(0);
}
public void setOverrideLimiter(boolean overrideLimiter) {
handle.getBooleans().write(0, overrideLimiter);
}
public boolean isAlwaysShow() {
return handle.getBooleans().read(1);
}
public void setAlwaysShow(boolean alwaysShow) {
handle.getBooleans().write(1, alwaysShow);
}
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);
}
public float getXDist() {
return handle.getFloat().read(0);
}
public void setXDist(float xDist) {
handle.getFloat().write(0, xDist);
}
public float getYDist() {
return handle.getFloat().read(1);
}
public void setYDist(float yDist) {
handle.getFloat().write(1, yDist);
}
public float getZDist() {
return handle.getFloat().read(2);
}
public void setZDist(float zDist) {
handle.getFloat().write(2, zDist);
}
public float getMaxSpeed() {
return handle.getFloat().read(3);
}
public void setMaxSpeed(float maxSpeed) {
handle.getFloat().write(3, maxSpeed);
}
public int getCount() {
return handle.getIntegers().read(0);
}
public void setCount(int count) {
handle.getIntegers().write(0, count);
}
public WrappedParticle<?> getParticle() {
return handle.getNewParticles().read(0);
}
public void setParticle(WrappedParticle<?> particle) {
handle.getNewParticles().write(0, particle);
}
}
@@ -0,0 +1,71 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.WrappedLevelChunkData;
import net.dmulloy2.protocol.AbstractPacket;
import net.minecraft.network.protocol.game.ClientboundLightUpdatePacket;
/**
* Wrapper for {@code ClientboundLightUpdatePacket} (game phase, clientbound).
*
* <p>NMS record: {@code ClientboundLightUpdatePacket(int x, int z, ClientboundLightUpdatePacketData lightData)}
*
* <p>Packet structure:
* <ul>
* <li>{@code int x} – chunk section X coordinate</li>
* <li>{@code int z} – chunk section Z coordinate</li>
* <li>{@code WrappedLevelChunkData.LightData lightData} – light engine data for the section</li>
* </ul>
*
* <p>Note: the NMS canonical constructor is not used because {@code ClientboundLightUpdatePacketData}
* may not be present in all supported server versions; all fields are set via ProtocolLib's
* {@code StructureModifier} after creating a default packet container.
*/
public class WrappedClientboundLightUpdatePacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.LIGHT_UPDATE;
public WrappedClientboundLightUpdatePacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundLightUpdatePacket(int x, int z, WrappedLevelChunkData.LightData lightData) {
this();
setX(x);
setZ(z);
setLightData(lightData);
}
public WrappedClientboundLightUpdatePacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getX() {
return handle.getIntegers().read(0);
}
public void setX(int x) {
handle.getIntegers().write(0, x);
}
public int getZ() {
return handle.getIntegers().read(1);
}
public void setZ(int z) {
handle.getIntegers().write(1, z);
}
/** Returns the light engine data for the section, or {@code null} if not set. */
public WrappedLevelChunkData.LightData getLightData() {
Object raw = handle.getModifier().read(2);
return raw != null ? BukkitConverters.getWrappedLightDataConverter().getSpecific(raw) : null;
}
public void setLightData(WrappedLevelChunkData.LightData lightData) {
handle.getModifier().write(2,
lightData != null ? BukkitConverters.getWrappedLightDataConverter().getGeneric(lightData) : null);
}
}
@@ -0,0 +1,127 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.BukkitConverters;
import java.util.Set;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.World;
/**
* Wrapper for {@code ClientboundLoginPacket} (game phase, clientbound).
*/
public class WrappedClientboundLoginPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.LOGIN;
public WrappedClientboundLoginPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundLoginPacket(int playerId, boolean hardcore, int maxPlayers, int chunkRadius, int simulationDistance, boolean reducedDebugInfo, boolean showDeathScreen, boolean doLimitedCrafting, boolean enforcesSecureChat, Set<World> levels) {
this();
setPlayerId(playerId);
setHardcore(hardcore);
setMaxPlayers(maxPlayers);
setChunkRadius(chunkRadius);
setSimulationDistance(simulationDistance);
setReducedDebugInfo(reducedDebugInfo);
setShowDeathScreen(showDeathScreen);
setDoLimitedCrafting(doLimitedCrafting);
setEnforcesSecureChat(enforcesSecureChat);
setLevels(levels);
}
public WrappedClientboundLoginPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getPlayerId() {
return handle.getIntegers().read(0);
}
public void setPlayerId(int playerId) {
handle.getIntegers().write(0, playerId);
}
public boolean isHardcore() {
return handle.getBooleans().read(0);
}
public void setHardcore(boolean hardcore) {
handle.getBooleans().write(0, hardcore);
}
public int getMaxPlayers() {
return handle.getIntegers().read(1);
}
public void setMaxPlayers(int maxPlayers) {
handle.getIntegers().write(1, maxPlayers);
}
public int getChunkRadius() {
return handle.getIntegers().read(2);
}
public void setChunkRadius(int chunkRadius) {
handle.getIntegers().write(2, chunkRadius);
}
public int getSimulationDistance() {
return handle.getIntegers().read(3);
}
public void setSimulationDistance(int simulationDistance) {
handle.getIntegers().write(3, simulationDistance);
}
public boolean isReducedDebugInfo() {
return handle.getBooleans().read(1);
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
handle.getBooleans().write(1, reducedDebugInfo);
}
public boolean isShowDeathScreen() {
return handle.getBooleans().read(2);
}
public void setShowDeathScreen(boolean showDeathScreen) {
handle.getBooleans().write(2, showDeathScreen);
}
public boolean isDoLimitedCrafting() {
return handle.getBooleans().read(3);
}
public void setDoLimitedCrafting(boolean doLimitedCrafting) {
handle.getBooleans().write(3, doLimitedCrafting);
}
public boolean isEnforcesSecureChat() {
return handle.getBooleans().read(4);
}
public void setEnforcesSecureChat(boolean enforcesSecureChat) {
handle.getBooleans().write(4, enforcesSecureChat);
}
/** Returns the set of all dimension levels/worlds the client is allowed to enter. */
public Set<World> getLevels() {
return handle.getSets(BukkitConverters.getWorldKeyConverter()).read(0);
}
public void setLevels(Set<World> levels) {
handle.getSets(BukkitConverters.getWorldKeyConverter()).write(0, levels);
}
// TODO: missing field 'commonPlayerSpawnInfo' (NMS type: CommonPlayerSpawnInfo)
// CommonPlayerSpawnInfo holds: Holder<DimensionType> dimensionType, ResourceKey<Level> dimension,
// long seed, GameType gameType, @Nullable GameType previousGameType, boolean isDebug,
// boolean isFlat, Optional<GlobalPos> lastDeathLocation, int portalCooldown, int seaLevel.
// This is a shared type used by both Login and Respawn packets.
// Add a WrappedCommonPlayerSpawnInfo wrapper class and a getCommonPlayerSpawnInfos() accessor
// in AbstractStructure, then expose it here.
}
@@ -0,0 +1,189 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.AutoWrapper;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import net.dmulloy2.protocol.AbstractPacket;
import com.comphenix.protocol.reflect.EquivalentConverter;
/**
* Wrapper for {@code ClientboundMapItemDataPacket} (Play phase, clientbound).
*
* <p>NMS structure (1.21+):
* <pre>
* record ClientboundMapItemDataPacket(
* MapId mapId, // field 0
* byte scale, // field 1
* boolean locked, // field 2
* Optional&lt;List&lt;MapDecoration&gt;&gt; decorations, // field 3
* Optional&lt;MapItemSavedData.MapPatch&gt; colorPatch) // field 4
*
* record MapId(int id)
* record MapDecoration(Holder&lt;MapDecorationType&gt; type, byte x, byte y, byte rot, Optional&lt;Component&gt; name)
* record MapItemSavedData.MapPatch(int startX, int startY, int width, int height, byte[] mapColors)
* </pre>
*
* <p>All five fields are accessed via {@link AutoWrapper} POJOs using the raw
* {@link PacketContainer#getModifier()} accessor (global field indices 0–4).
* {@link WrappedMapDecoration#type} ({@code Holder&lt;MapDecorationType&gt;}) and
* {@link WrappedMapDecoration#name} ({@code Optional&lt;Component&gt;}) are kept as raw
* {@link Object} pass-throughs to avoid NMS compile-time dependencies.
*/
public class WrappedClientboundMapItemDataPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.MAP;
// AutoWrappers for the nested NMS record types.
private static final AutoWrapper<WrappedMapId> MAP_ID_WRAPPER =
AutoWrapper.wrap(WrappedMapId.class, "world.level.saveddata.maps.MapId");
private static final AutoWrapper<WrappedMapDecoration> MAP_DECORATION_WRAPPER =
AutoWrapper.wrap(WrappedMapDecoration.class, "world.level.saveddata.maps.MapDecoration");
private static final AutoWrapper<WrappedMapPatch> MAP_PATCH_WRAPPER =
AutoWrapper.wrap(WrappedMapPatch.class, "world.level.saveddata.maps.MapItemSavedData$MapPatch");
public WrappedClientboundMapItemDataPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundMapItemDataPacket(int mapId, byte scale, boolean locked, Optional<List<WrappedMapDecoration>> decorations, Optional<WrappedMapPatch> colorPatch) {
this();
setMapId(mapId);
setScale(scale);
setLocked(locked);
setDecorations(decorations);
setColorPatch(colorPatch);
}
public WrappedClientboundMapItemDataPacket(int mapId, byte scale, boolean locked) {
this(mapId, scale, locked, Optional.empty(), Optional.empty());
}
public WrappedClientboundMapItemDataPacket(PacketContainer packet) {
super(packet, TYPE);
}
// ---- mapId (global field 0) ---------------------------------------------
/** Returns the map id from the wrapped {@code MapId} record. */
public int getMapId() {
return MAP_ID_WRAPPER.getSpecific(handle.getModifier().read(0)).id;
}
public void setMapId(int mapId) {
WrappedMapId w = new WrappedMapId();
w.id = mapId;
handle.getModifier().write(0, MAP_ID_WRAPPER.getGeneric(w));
}
// ---- scale (global field 1) ---------------------------------------------
public byte getScale() {
return handle.getBytes().read(0);
}
public void setScale(byte scale) {
handle.getBytes().write(0, scale);
}
// ---- locked (global field 2) --------------------------------------------
public boolean isLocked() {
return handle.getBooleans().read(0);
}
public void setLocked(boolean locked) {
handle.getBooleans().write(0, locked);
}
// ---- decorations (global field 3) ---------------------------------------
/**
* Returns the map decorations.
* The {@link WrappedMapDecoration#type} and {@link WrappedMapDecoration#name} fields
* are raw NMS objects ({@code Holder<MapDecorationType>} and {@code Optional<Component>}
* respectively) — pass them through unchanged when writing back.
*/
@SuppressWarnings("unchecked")
public Optional<List<WrappedMapDecoration>> getDecorations() {
Optional<List<Object>> raw = (Optional<List<Object>>) handle.getModifier().read(3);
if (raw == null) return Optional.empty();
return raw.map(list -> list.stream()
.map(MAP_DECORATION_WRAPPER::getSpecific)
.collect(Collectors.toList()));
}
public void setDecorations(Optional<List<WrappedMapDecoration>> decorations) {
Optional<List<Object>> nms = decorations.map(list -> list.stream()
.map(MAP_DECORATION_WRAPPER::getGeneric)
.collect(Collectors.toList()));
handle.getModifier().write(3, nms);
}
// ---- colorPatch (global field 4) ----------------------------------------
@SuppressWarnings("unchecked")
public Optional<WrappedMapPatch> getColorPatch() {
Optional<Object> raw = (Optional<Object>) handle.getModifier().read(4);
if (raw == null) return Optional.empty();
return raw.map(MAP_PATCH_WRAPPER::getSpecific);
}
public void setColorPatch(Optional<WrappedMapPatch> colorPatch) {
Optional<Object> nms = colorPatch.map(MAP_PATCH_WRAPPER::getGeneric);
handle.getModifier().write(4, nms);
}
// ---- inner POJOs --------------------------------------------------------
/**
* POJO mirroring {@code record MapId(int id)}.
* Field {@code id} must be declared first (and only) to match the NMS record layout.
*/
public static final class WrappedMapId {
public int id;
public WrappedMapId() {
}
}
/**
* POJO mirroring
* {@code record MapDecoration(Holder<MapDecorationType> type, byte x, byte y, byte rot, Optional<Component> name)}.
*
* <p>Fields must be declared in the same order as the NMS record.
* {@code type} ({@code Holder<MapDecorationType>}) and {@code name} ({@code Optional<Component>})
* are typed as {@link Object} — they are passed through the AutoWrapper without conversion.
*/
public static final class WrappedMapDecoration {
/** NMS {@code Holder<MapDecorationType>} — pass-through as raw {@link Object}. */
public Object type;
public byte x;
public byte y;
public byte rot;
/** NMS {@code Optional<Component>} — pass-through as raw {@link Object}. */
public Object name;
public WrappedMapDecoration() {
}
}
/**
* POJO mirroring
* {@code record MapItemSavedData.MapPatch(int startX, int startY, int width, int height, byte[] mapColors)}.
*
* <p>Fields must be declared in the same order as the NMS record.
*/
public static final class WrappedMapPatch {
public int startX;
public int startY;
public int width;
public int height;
public byte[] mapColors;
public WrappedMapPatch() {
}
}
}
@@ -0,0 +1,81 @@
package net.dmulloy2.protocol.wrappers.game.clientbound;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import java.util.List;
import net.dmulloy2.protocol.AbstractPacket;
import org.bukkit.inventory.MerchantRecipe;
/**
* Wrapper for {@code ClientboundMerchantOffersPacket} (game phase, clientbound).
*/
public class WrappedClientboundMerchantOffersPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.OPEN_WINDOW_MERCHANT;
public WrappedClientboundMerchantOffersPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundMerchantOffersPacket(int containerId, List<MerchantRecipe> offers, int villagerLevel, int villagerXp, boolean showProgress, boolean canRestock) {
this();
setContainerId(containerId);
setOffers(offers);
setVillagerLevel(villagerLevel);
setVillagerXp(villagerXp);
setShowProgress(showProgress);
setCanRestock(canRestock);
}
public WrappedClientboundMerchantOffersPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getContainerId() {
return handle.getIntegers().read(0);
}
public void setContainerId(int containerId) {
handle.getIntegers().write(0, containerId);
}
public List<MerchantRecipe> getOffers() {
return handle.getMerchantRecipeLists().read(0);
}
public void setOffers(List<MerchantRecipe> offers) {
handle.getMerchantRecipeLists().write(0, offers);
}
public int getVillagerLevel() {
return handle.getIntegers().read(1);
}
public void setVillagerLevel(int villagerLevel) {
handle.getIntegers().write(1, villagerLevel);
}
public int getVillagerXp() {
return handle.getIntegers().read(2);
}
public void setVillagerXp(int villagerXp) {
handle.getIntegers().write(2, villagerXp);
}
public boolean isShowProgress() {
return handle.getBooleans().read(0);
}
public void setShowProgress(boolean showProgress) {
handle.getBooleans().write(0, showProgress);
}
public boolean isCanRestock() {
return handle.getBooleans().read(1);
}
public void setCanRestock(boolean canRestock) {
handle.getBooleans().write(1, canRestock);
}
}
@@ -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 ClientboundMountScreenOpenPacket} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int windowId} – window ID for the horse inventory</li>
* <li>{@code int containerSize} – number of slots in the horse container</li>
* <li>{@code int entityId} – entity ID of the horse</li>
* </ul>
*/
public class WrappedClientboundMountScreenOpenPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.OPEN_WINDOW_HORSE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(int.class)
.withParam(int.class);
public WrappedClientboundMountScreenOpenPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundMountScreenOpenPacket(int windowId, int containerSize, int entityId) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(windowId, containerSize, entityId)));
}
public WrappedClientboundMountScreenOpenPacket(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 getContainerSize() {
return handle.getIntegers().read(1);
}
public void setContainerSize(int containerSize) {
handle.getIntegers().write(1, containerSize);
}
public int getEntityId() {
return handle.getIntegers().read(2);
}
public void setEntityId(int entityId) {
handle.getIntegers().write(2, entityId);
}
}
@@ -0,0 +1,89 @@
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 ClientboundMoveEntityPacket.Pos} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int entityId} – entity ID</li>
* <li>{@code short dx} – delta X in units of 1/4096 of a block</li>
* <li>{@code short dy} – delta Y in units of 1/4096 of a block</li>
* <li>{@code short dz} – delta Z in units of 1/4096 of a block</li>
* <li>{@code boolean onGround} – whether the entity is on the ground</li>
* </ul>
*
* <p>To convert from blocks: {@code (short)(deltaBlocks * 4096)}.
*/
public class WrappedClientboundMoveEntityPosPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.REL_ENTITY_MOVE;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(short.class)
.withParam(short.class)
.withParam(short.class)
.withParam(boolean.class);
public WrappedClientboundMoveEntityPosPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundMoveEntityPosPacket(int entityId, short dx, short dy, short dz, boolean onGround) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(entityId, dx, dy, dz, onGround)));
}
public WrappedClientboundMoveEntityPosPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getEntityId() {
return handle.getIntegers().read(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().write(0, entityId);
}
/** @return delta X in units of 1/4096 of a block */
public short getDx() {
return handle.getShorts().read(0);
}
/** @param dx delta X in units of 1/4096 of a block */
public void setDx(short dx) {
handle.getShorts().write(0, dx);
}
/** @return delta Y in units of 1/4096 of a block */
public short getDy() {
return handle.getShorts().read(1);
}
/** @param dy delta Y in units of 1/4096 of a block */
public void setDy(short dy) {
handle.getShorts().write(1, dy);
}
/** @return delta Z in units of 1/4096 of a block */
public short getDz() {
return handle.getShorts().read(2);
}
/** @param dz delta Z in units of 1/4096 of a block */
public void setDz(short dz) {
handle.getShorts().write(2, dz);
}
public boolean isOnGround() {
return handle.getBooleans().read(0);
}
public void setOnGround(boolean onGround) {
handle.getBooleans().write(0, onGround);
}
}
@@ -0,0 +1,107 @@
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 ClientboundMoveEntityPacket.PosRot} (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int entityId} – entity ID</li>
* <li>{@code short dx} – delta X in units of 1/4096 of a block</li>
* <li>{@code short dy} – delta Y in units of 1/4096 of a block</li>
* <li>{@code short dz} – delta Z in units of 1/4096 of a block</li>
* <li>{@code byte yaw} – packed yaw angle</li>
* <li>{@code byte pitch} – packed pitch angle</li>
* <li>{@code boolean onGround} – whether the entity is on the ground</li>
* </ul>
*/
public class WrappedClientboundMoveEntityPosRotPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.REL_ENTITY_MOVE_LOOK;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(short.class)
.withParam(short.class)
.withParam(short.class)
.withParam(byte.class)
.withParam(byte.class)
.withParam(boolean.class);
public WrappedClientboundMoveEntityPosRotPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundMoveEntityPosRotPacket(int entityId, short dx, short dy, short dz, byte yaw, byte pitch, boolean onGround) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(entityId, dx, dy, dz, yaw, pitch, onGround)));
}
public WrappedClientboundMoveEntityPosRotPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getEntityId() {
return handle.getIntegers().read(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().write(0, entityId);
}
/** @return delta X in units of 1/4096 of a block */
public short getDx() {
return handle.getShorts().read(0);
}
/** @param dx delta X in units of 1/4096 of a block */
public void setDx(short dx) {
handle.getShorts().write(0, dx);
}
/** @return delta Y in units of 1/4096 of a block */
public short getDy() {
return handle.getShorts().read(1);
}
/** @param dy delta Y in units of 1/4096 of a block */
public void setDy(short dy) {
handle.getShorts().write(1, dy);
}
/** @return delta Z in units of 1/4096 of a block */
public short getDz() {
return handle.getShorts().read(2);
}
/** @param dz delta Z in units of 1/4096 of a block */
public void setDz(short dz) {
handle.getShorts().write(2, dz);
}
public byte getYaw() {
return handle.getBytes().read(0);
}
public void setYaw(byte yaw) {
handle.getBytes().write(0, yaw);
}
public byte getPitch() {
return handle.getBytes().read(1);
}
public void setPitch(byte pitch) {
handle.getBytes().write(1, pitch);
}
public boolean isOnGround() {
return handle.getBooleans().read(0);
}
public void setOnGround(boolean onGround) {
handle.getBooleans().write(0, onGround);
}
}
@@ -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.injector.EquivalentConstructor;
import net.dmulloy2.protocol.AbstractPacket;
/**
* Wrapper for {@code ClientboundRotateHeadPacket} / entity look packet
* (Play phase, clientbound).
*
* <p>Packet structure:
* <ul>
* <li>{@code int entityId} – entity ID</li>
* <li>{@code byte yaw} – packed yaw (use {@link WrappedClientboundAddEntityPacket#angleToByte}
* / {@link WrappedClientboundAddEntityPacket#byteToAngle} for conversion)</li>
* <li>{@code byte pitch} – packed pitch</li>
* <li>{@code boolean onGround} – whether the entity is on the ground</li>
* </ul>
*/
public class WrappedClientboundMoveEntityRotPacket extends AbstractPacket {
public static final PacketType TYPE = PacketType.Play.Server.ENTITY_LOOK;
private static final EquivalentConstructor CONSTRUCTOR = new EquivalentConstructor(TYPE)
.withParam(int.class)
.withParam(byte.class)
.withParam(byte.class)
.withParam(boolean.class);
public WrappedClientboundMoveEntityRotPacket() {
super(new PacketContainer(TYPE), TYPE);
}
public WrappedClientboundMoveEntityRotPacket(int entityId, byte yaw, byte pitch, boolean onGround) {
this(PacketContainer.fromPacket(CONSTRUCTOR.create(entityId, yaw, pitch, onGround)));
}
public WrappedClientboundMoveEntityRotPacket(PacketContainer packet) {
super(packet, TYPE);
}
public int getEntityId() {
return handle.getIntegers().read(0);
}
public void setEntityId(int entityId) {
handle.getIntegers().write(0, entityId);
}
public byte getYaw() {
return handle.getBytes().read(0);
}
public void setYaw(byte yaw) {
handle.getBytes().write(0, yaw);
}
public byte getPitch() {
return handle.getBytes().read(1);
}
public void setPitch(byte pitch) {
handle.getBytes().write(1, pitch);
}
public boolean isOnGround() {
return handle.getBooleans().read(0);
}
public void setOnGround(boolean onGround) {
handle.getBooleans().write(0, onGround);
}
}
Loaded 100 of 507 files, more files were not shown because too many files have changed in this diff. Show more