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