mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2026-11-04 08:52:09 -05:00
Compare 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 | ||
|
|
98856a8b9f | ||
|
|
5ec23ef33b | ||
|
|
589ee12e12 | ||
|
|
6cf7c7d8c3 | ||
|
|
3139e82aea | ||
|
|
3385fe664b | ||
|
|
0e267a5f1a | ||
|
|
2d3b3e3363 | ||
|
|
8eeb82f89d | ||
|
|
b0b9b666a7 | ||
|
|
b723ff3351 | ||
|
|
72fd0709b6 | ||
|
|
5a9afed462 | ||
|
|
9404ce32db | ||
|
|
6c6bde522e | ||
|
|
f911ee904c | ||
|
|
22012d3dc9 | ||
|
|
c52b4bb10c | ||
|
|
b9f96469f5 | ||
|
|
e5413ccc5b | ||
|
|
b37a3bcb13 | ||
|
|
19cb81b6ab | ||
|
|
f606cc954d | ||
|
|
774e679571 | ||
|
|
1257506929 | ||
|
|
5773bcbc16 | ||
|
|
468082c9f9 | ||
|
|
a11b403c4b | ||
|
|
aa4fab1632 | ||
|
|
771a530fe2 | ||
|
|
ca9b5bbb80 | ||
|
|
d790ded81f | ||
|
|
f357ac85d0 | ||
|
|
66cdb5b69d | ||
|
|
1aeb002afe | ||
|
|
79d44f0d6e | ||
|
|
9bde9b4231 | ||
|
|
714252df6f | ||
|
|
e56bcf624d | ||
|
|
596e5e4246 | ||
|
|
f3f895995a | ||
|
|
67e7375b2f | ||
|
|
9417eee444 | ||
|
|
a432fae4dd | ||
|
|
8a5e59d4f2 | ||
|
|
90a386b743 | ||
|
|
a80762feed | ||
|
|
a936183b76 | ||
|
|
804aa5df11 | ||
|
|
1e73df8600 | ||
|
|
4314a55866 | ||
|
|
2c0d632dc2 | ||
|
|
fd5d8c576e | ||
|
|
0256953056 | ||
|
|
24262d3f7d | ||
|
|
7a85663ded | ||
|
|
bcff6c6719 | ||
|
|
3155e76d06 | ||
|
|
6845acd89d |
No files matched your search
@@ -1 +1,2 @@
|
||||
* text=auto eol=lf
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
+73
-36
@@ -1,38 +1,75 @@
|
||||
Contributing to ProtocolLib
|
||||
===========================
|
||||
This page will detail specific things that must be done if you intend to contribute to this project.
|
||||
# Contributing to ProtocolLib
|
||||
|
||||
## Updating ProtocolLib to a New Protocol Version
|
||||
#### Before we get started:
|
||||
1. **When do we need to update the protocol version?**
|
||||
_A Minecraft protocol version is a number for each specification of the Minecraft protocol.
|
||||
This means it is incremented when there is a change to the [Protocol Specification](http://wiki.vg/Protocol),
|
||||
but not necessarily for each Minecraft version update. If the latest Minecraft version contains a protocol change,
|
||||
that means ProtocolLib needs to be updated._
|
||||
2. **When do we need to update other parts of ProtocolLib?**
|
||||
_When the package version guard changes. The package version guard is the version string inserted
|
||||
into the package identifier for CraftBukkit and net.minecraft.server (e.g. v1_9_R2). It is incremented
|
||||
by Spigot if there is a major breaking change in the internal server code. This is to encourage plugins
|
||||
that interact directly with CraftBukkit/nms code to update more specifically. However, since most of
|
||||
ProtocolLib uses reflection, and the structure of packet classes is generally static, one will have
|
||||
to go through the code and change imports from nms/CraftBukkit._
|
||||
ProtocolLib supports a wide range of Minecraft versions through reflection and compatibility wrappers. Changes to packet
|
||||
registration, reflection, or public wrappers can affect plugins on versions other than the one currently used for
|
||||
development, so keep changes focused and preserve existing behavior unless a break is unavoidable.
|
||||
|
||||
#### Ready? Let's get started!
|
||||
1. Read the [Protocol Changes](http://wiki.vg/Protocol_History). Always make sure the list is both
|
||||
complete and correct. If you're unsure, don't hesitate to ask in #mcdevs (the people who maintain wiki.vg) on [libera.chat](http://libera.chat)
|
||||
or #spigot on [irc.spi.gt](http://irc.spi.gt) ([webchat](https://irc.spi.gt/iris/?channels=spigot)).
|
||||
2. Search for usages of the now-defunct NMS package guard and change them.
|
||||
3. The class `com.comphenix.protocol.PacketType` contains a list of all the packets. If any packets were added or removed
|
||||
(or had their ID changed), make sure to update this list. If a packet was removed
|
||||
in favor of usage of another packet, instead of removing it, move it to the bottom of the list
|
||||
in its section, add a deprecation warning to it, and redirect it to the packet that replaced it.
|
||||
4. `mvn` in the root directory to build the project.
|
||||
5. If tests fail in the maven build, go through the tests to make sure you removed references to any removed packets
|
||||
and changed the NMS version guards.
|
||||
6. Increment the package version in `com.comphenix.protocol.utility.Constants`.
|
||||
7. `com.comphenix.protocol.ProtocolLibrary` contains several constants that must be updated, including the Minecraft version
|
||||
and the release date.
|
||||
8. `com.comphenix.protocol.utility.MinecraftProtocolVersion` contains a map of all the protocol version integers.
|
||||
If the protocol version has been incremented, add a new line to the map.
|
||||
9. `mvn` in root directory again. If it builds successfully, test on the appropriate version of a Spigot server. If
|
||||
the build fails, debug!
|
||||
## Building ProtocolLib
|
||||
|
||||
Use the Gradle wrapper from the repository root. The 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` 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.
|
||||
|
||||
## Updating ProtocolLib for a Minecraft Release
|
||||
|
||||
Start by reviewing the [Java Edition protocol](https://minecraft.wiki/w/Java_Edition_protocol) and the corresponding
|
||||
server classes. The server packet classes and codecs are authoritative when documentation and implementation differ.
|
||||
|
||||
### 1. Update version metadata
|
||||
|
||||
- Update `mcVersion` in `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.
|
||||
|
||||
### 2. Reconcile packet types
|
||||
|
||||
`PacketType` must contain a matching definition for every packet in the current server protocol registries. Compare the
|
||||
serverbound and clientbound packet classes and codecs against every protocol phase, including configuration.
|
||||
|
||||
- Add new, renamed, or split packets and update their current IDs and aliases.
|
||||
- Do not remove an existing public `PacketType` constant. Move removed types to the deprecated section and document their
|
||||
replacement when one exists.
|
||||
- Represent split packets as separate packet types instead of redirecting incompatible packet shapes.
|
||||
- Run the packet registry tests after changing packet definitions. A missing definition can prevent later packets in the
|
||||
same protocol registry from being registered.
|
||||
|
||||
### 3. Update reflection and wrappers
|
||||
|
||||
- Inspect changed fields, constructors, records, nested classes, and stream codecs instead of relying only on class names.
|
||||
- Prefer structural or type-based reflection where names are unstable.
|
||||
- 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.
|
||||
- Keep converters symmetric and preserve wrapper cloning and equality semantics.
|
||||
- A custom `StructureModifier` must maintain the complete modifier contract, including safe reads and writes, field count,
|
||||
metadata, introspection, defaults, and `withTarget()`.
|
||||
|
||||
### 4. Add regression coverage
|
||||
|
||||
- Initialize the test environment with `BukkitInitialization` when tests access CraftBukkit or NMS classes.
|
||||
- Exercise the actual packet structure supplied by the current 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`.
|
||||
- Before declaring a Minecraft release supported, build the shaded plugin and test it on the corresponding server.
|
||||
|
||||
## Pull Request Expectations
|
||||
|
||||
- Keep the change limited to the reported problem or release update.
|
||||
- Include regression tests for packet, reflection, converter, or wrapper behavior changes.
|
||||
- Preserve public API signatures and binary compatibility where possible.
|
||||
- Clearly document any unavoidable compatibility change.
|
||||
- Do not hide reflection failures with broad exception handling or silent fallbacks.
|
||||
@@ -1,8 +1,10 @@
|
||||
name: ProtocolLib full build lifecycle
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -15,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
|
||||
@@ -25,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
|
||||
@@ -0,0 +1,46 @@
|
||||
name: ProtocolLib development build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Run gradle build lifecycle
|
||||
run: |
|
||||
COMMIT_SHA=${{ github.sha }} ./gradlew build shadowJar --no-daemon
|
||||
|
||||
- name: Upload plugin file
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ProtocolLib
|
||||
path: |
|
||||
build/libs/ProtocolLib.jar
|
||||
build/libs/ProtocolLib-Spigot.jar
|
||||
|
||||
- name: Create or update GitHub pre-release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
COMMIT_MSG=$(git log -1 --pretty=%B)
|
||||
SHORT_HASH=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
RELEASE_NOTES=$(gh release view dev-build --repo ${{ github.repository }} --json body -q ".body" 2>/dev/null || echo "")
|
||||
gh release view dev-build --repo ${{ github.repository }} || \
|
||||
gh release create dev-build --prerelease --title "Development Build" --notes "## Changelog" --repo ${{ github.repository }}
|
||||
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 }}
|
||||
@@ -181,4 +181,3 @@ logs/
|
||||
build/
|
||||
logs/
|
||||
.java-version
|
||||
gradle.properties
|
||||
@@ -0,0 +1,124 @@
|
||||
# ProtocolLib Agent Guide
|
||||
|
||||
This file applies to the entire repository. Read `.github/CONTRIBUTING.md` before changing Minecraft support, packet
|
||||
definitions, reflection, or public wrappers.
|
||||
|
||||
## Repository Basics
|
||||
|
||||
- The main project uses the Gradle wrapper. Do not use `TinyProtocol/pom.xml` to build ProtocolLib.
|
||||
- The Java toolchain and current Spigot dependency are defined in `build.gradle.kts`.
|
||||
- Keep the Java 25 build toolchain separate from the Java 17 production bytecode target.
|
||||
- Production code is under `src/main/java`; JUnit tests and NMS test initialization are under `src/test/java`.
|
||||
- Keep changes focused. Do not combine protocol fixes with unrelated cleanup or formatting.
|
||||
- Preserve public API and binary compatibility unless the task explicitly requires a breaking change.
|
||||
|
||||
## Build and Test Commands
|
||||
|
||||
```shell
|
||||
./gradlew test
|
||||
./gradlew build shadowJar
|
||||
```
|
||||
|
||||
Run the smallest relevant test while developing:
|
||||
|
||||
```shell
|
||||
./gradlew test --tests fully.qualified.TestClass
|
||||
./gradlew test --tests fully.qualified.TestClass.testMethod
|
||||
```
|
||||
|
||||
After changing production Java code, run the targeted tests and then the complete test suite. Use `build shadowJar` when
|
||||
the task affects packaging, dependencies, release support, or the final plugin artifact.
|
||||
|
||||
Do not introduce another build system, test runner, formatter, or lint tool unless the repository adopts it separately.
|
||||
|
||||
## Important Code Areas
|
||||
|
||||
- `PacketType.java`: public packet constants, current IDs, aliases, and deprecated packet definitions.
|
||||
- `injector/packet/internal/ProtocolRegistry_1_20_5.java`: current packet discovery and codec registration.
|
||||
- `utility/MinecraftVersion.java`: named Minecraft versions and the latest supported release.
|
||||
- `utility/MinecraftProtocolVersion.java`: Minecraft release to protocol-number mappings.
|
||||
- `ProtocolLibrary.java`: supported version bounds and latest release date.
|
||||
- `utility/MinecraftReflection.java`: centralized NMS and CraftBukkit class discovery.
|
||||
- `reflect/StructureModifier.java`: indexed packet field access and conversion contract.
|
||||
- `wrappers/`: stable public representations of unstable NMS structures.
|
||||
- `events/PacketContainer.java` and `events/AbstractStructure.java`: packet construction and typed field access.
|
||||
- `src/test/java/com/comphenix/protocol/BukkitInitialization.java`: test bootstrap for CraftBukkit and NMS access.
|
||||
|
||||
## Minecraft Release Updates
|
||||
|
||||
Follow the complete checklist in `.github/CONTRIBUTING.md`. In particular:
|
||||
|
||||
- Update all version metadata together; do not bump only the Gradle dependency.
|
||||
- Reconcile every serverbound and clientbound packet in every protocol phase with `PacketType`.
|
||||
- Add packet types for newly split packets instead of forcing incompatible shapes through an older packet constant.
|
||||
- Preserve removed public `PacketType` constants in the deprecated section.
|
||||
- Update `MinecraftProtocolVersion` only when the protocol number actually changed.
|
||||
|
||||
Every packet exposed by the current NMS protocol registries must have a matching `PacketType`. An undefined or incorrectly
|
||||
renamed packet can abort registration of later packets in the same registry.
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
- Put version checks at the narrowest affected boundary.
|
||||
- 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.
|
||||
- If Minecraft removed a concept, return an unsupported or absent result consistent with existing APIs. Do not claim an
|
||||
unrelated packet or class is the removed type.
|
||||
- When Minecraft splits one packet into several packets, expose and use the new packet types explicitly.
|
||||
- Preserve unrelated packet fields when adapting or rewriting one logical value.
|
||||
|
||||
The current test dependency exercises the latest supported server. Review older guarded paths carefully because they may
|
||||
not execute in the local test run.
|
||||
|
||||
## Packet and Reflection Rules
|
||||
|
||||
- Treat the current server packet classes, record components, constructors, and codecs as authoritative.
|
||||
- Prefer existing `MinecraftReflection`, `FuzzyReflection`, and accessor helpers over new direct reflection.
|
||||
- Prefer structural and type-based matching where obfuscated names are unstable.
|
||||
- Search for an existing class resolver, converter, or accessor before adding another one.
|
||||
- Cache stable reflective lookups following nearby patterns; do not repeatedly scan classes on packet hot paths.
|
||||
- Do not hide reflection failures with broad catches, silent defaults, or unrelated fallback types.
|
||||
- Account for static fields, inherited fields, records, nested implementations, and duplicate field types when writing
|
||||
fuzzy contracts.
|
||||
|
||||
## `StructureModifier` Contract
|
||||
|
||||
Typed accessors returned from packet APIs must behave like normal `StructureModifier` instances.
|
||||
|
||||
- `size()`, `getFields()`, `getField()`, target metadata, and field metadata must agree.
|
||||
- `readSafely()` returns `null` for an absent index.
|
||||
- `writeSafely()` ignores an absent index without throwing.
|
||||
- `withTarget()` must preserve the modifier's behavior for the new target.
|
||||
- `writeDefaults()` must remain safe.
|
||||
- Converters must be applied symmetrically on reads and writes.
|
||||
|
||||
If a packet no longer contains a real backing field, prefer an empty modifier when compatible. If a virtual field is
|
||||
required for API compatibility, initialize all modifier metadata and test its inherited methods.
|
||||
|
||||
## Wrapper and Converter Rules
|
||||
|
||||
- Wrapper factories, converters, getters, setters, and `deepClone()` must describe the same logical value.
|
||||
- Preserve converter symmetry: converting specific to generic and back must not unexpectedly lose represented state.
|
||||
- If a wrapper handle changes from a nested value to an entire packet, define logical `equals()` and `hashCode()` so
|
||||
unrelated packet metadata does not affect equality.
|
||||
- Clones that represent the same logical value should compare equal.
|
||||
- Do not fabricate legacy states that the modern wire format cannot distinguish; document or surface the limitation.
|
||||
- Record or immutable packet updates must retain entity IDs, flags, and other components not owned by the wrapper.
|
||||
|
||||
## Tests
|
||||
|
||||
- Call `BukkitInitialization.initializeAll()` before tests that resolve CraftBukkit or NMS classes.
|
||||
- Prefer regression tests using the actual Spigot packet classes supplied by the test dependency.
|
||||
- Test reads, writes, safe access, cloning, equality, and preservation of unrelated fields when changing wrappers.
|
||||
- Test modifier metadata and inherited methods when introducing a custom `StructureModifier`.
|
||||
- Re-enable disabled tests when fixing the incompatibility that caused them to be disabled.
|
||||
- Do not weaken assertions or encode known incorrect behavior merely to make a new Minecraft version pass.
|
||||
|
||||
## Documentation and Review
|
||||
|
||||
- Update Javadocs when version support, nullability, or unsupported behavior changes.
|
||||
- Update `.github/CONTRIBUTING.md` when the repository-wide build or release workflow changes.
|
||||
- Keep this file operational and durable; do not add task-specific notes, temporary workarounds, or current PR details.
|
||||
- Before finishing, inspect the complete diff for accidental API changes and unrelated edits.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modifying Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](https://wiki.vg/Protocol), or inject custom packets into the
|
||||
and outgoing packets, or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
@@ -12,16 +12,21 @@ lead to more subtle bugs.
|
||||
|
||||
### Resources
|
||||
|
||||
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
* [Dev Builds](https://ci.dmulloy2.net/job/ProtocolLib)
|
||||
* [Spigot Page](https://spigotmc.org/resources/protocollib.1997/)
|
||||
* [Releases](https://github.com/dmulloy2/ProtocolLib/releases)
|
||||
* [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
|
||||
|
||||
@@ -42,18 +47,11 @@ depend: [ ProtocolLib ]
|
||||
You can also add ProtocolLib as a Maven dependency:
|
||||
|
||||
````xml
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<groupId>net.dmulloy2</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.1.0</version>
|
||||
<version>5.4.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -63,14 +61,56 @@ Or use the maven dependency with gradle:
|
||||
|
||||
```gradle
|
||||
repositories {
|
||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
|
||||
compileOnly 'net.dmulloy2:ProtocolLib:5.4.0'
|
||||
}
|
||||
```
|
||||
|
||||
#### 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
|
||||
@@ -97,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);
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
@@ -64,7 +64,7 @@ public abstract class TinyProtocol {
|
||||
|
||||
// Packets we have to intercept
|
||||
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getClass("{nms}.PacketLoginInStart", "net.minecraft.network.protocol.login.PacketLoginInStart");
|
||||
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
|
||||
private static final FieldAccessor<String> getPlayerName = new PlayerNameAccessor();
|
||||
|
||||
// Speedup channel lookup
|
||||
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap();
|
||||
@@ -492,6 +492,59 @@ public abstract class TinyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the player name from the login start packet.
|
||||
* This fixes the issue from 1.19 where the GameProfile field has been removed from this login packet.
|
||||
*
|
||||
* @author gamerover98
|
||||
*/
|
||||
private static class PlayerNameAccessor implements FieldAccessor<String> {
|
||||
|
||||
private FieldAccessor<String> getPlayerName;
|
||||
private FieldAccessor<GameProfile> getGameProfile;
|
||||
|
||||
PlayerNameAccessor() {
|
||||
try {
|
||||
this.getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
|
||||
} catch (IllegalArgumentException illegalArgumentException) {
|
||||
// nothing to do.
|
||||
}
|
||||
|
||||
try {
|
||||
//HOT-FIX for 1.19+
|
||||
this.getPlayerName = Reflection.getField(PACKET_LOGIN_IN_START, String.class, 0);
|
||||
} catch (IllegalArgumentException illegalArgumentException) {
|
||||
// nothing to do.
|
||||
}
|
||||
|
||||
if (getGameProfile == null && getPlayerName == null) {
|
||||
throw new UnsupportedOperationException("The current server version is not supported by TinyProtocol");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(Object target) {
|
||||
if (getPlayerName != null) {
|
||||
String playerName = getPlayerName.get(target);
|
||||
return playerName.substring(0, Math.min(16, playerName.length()));
|
||||
}
|
||||
|
||||
return getGameProfile.get(target).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Object target, Object value) {
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasField(Object target) {
|
||||
return getPlayerName != null
|
||||
? getPlayerName.hasField(target)
|
||||
: getGameProfile.hasField(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
|
||||
*
|
||||
@@ -533,8 +586,7 @@ public abstract class TinyProtocol {
|
||||
|
||||
private void handleLoginStart(Channel channel, Object packet) {
|
||||
if (PACKET_LOGIN_IN_START.isInstance(packet)) {
|
||||
GameProfile profile = getGameProfile.get(packet);
|
||||
channelLookup.put(profile.getName(), channel);
|
||||
channelLookup.put(getPlayerName.get(packet), channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'com.gradleup.shadow' version '8.3.2'
|
||||
}
|
||||
|
||||
group = 'com.comphenix.protocol'
|
||||
version = '5.3.0'
|
||||
description = 'Provides access to the Minecraft protocol'
|
||||
|
||||
def isSnapshot = version.endsWith('-SNAPSHOT')
|
||||
|
||||
repositories {
|
||||
// mavenLocal() // can speed up build, but may fail in CI
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
url 'https://repo.dmulloy2.net/repository/public/'
|
||||
}
|
||||
|
||||
maven {
|
||||
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
|
||||
}
|
||||
|
||||
maven {
|
||||
url 'https://libraries.minecraft.net/'
|
||||
metadataSources {
|
||||
mavenPom()
|
||||
artifact()
|
||||
ignoreGradleMetadataRedirection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.bytebuddy:byte-buddy:1.15.1'
|
||||
compileOnly 'org.spigotmc:spigot-api:1.21.1-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.spigotmc:spigot:1.21.1-R0.1-SNAPSHOT'
|
||||
compileOnly 'io.netty:netty-all:4.0.23.Final'
|
||||
compileOnly 'net.kyori:adventure-text-serializer-gson:4.14.0'
|
||||
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0'
|
||||
testImplementation 'org.mockito:mockito-core:5.6.0'
|
||||
testImplementation 'io.netty:netty-common:4.1.97.Final'
|
||||
testImplementation 'io.netty:netty-transport:4.1.97.Final'
|
||||
testImplementation 'org.spigotmc:spigot:1.21.1-R0.1-SNAPSHOT'
|
||||
testImplementation 'net.kyori:adventure-text-serializer-gson:4.14.0'
|
||||
testImplementation 'net.kyori:adventure-text-serializer-plain:4.14.0'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'paperweight-mappings-namespace': 'mojang'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependencies {
|
||||
include(dependency('net.bytebuddy:byte-buddy:.*'))
|
||||
}
|
||||
relocate 'net.bytebuddy', 'com.comphenix.net.bytebuddy'
|
||||
archiveFileName = 'ProtocolLib.jar'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
exceptionFormat = 'full'
|
||||
}
|
||||
}
|
||||
|
||||
compileTestJava {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
processResources {
|
||||
def includeBuild = isSnapshot && System.getenv('BUILD_NUMBER')
|
||||
def fullVersion = includeBuild
|
||||
? version + '-' + System.getenv('BUILD_NUMBER')
|
||||
: version
|
||||
|
||||
eachFile { expand(['version': fullVersion]) }
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
|
||||
afterEvaluate {
|
||||
artifactId = 'ProtocolLib'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url isSnapshot
|
||||
? 'https://repo.dmulloy2.net/repository/snapshots/'
|
||||
: 'https://repo.dmulloy2.net/repository/releases/'
|
||||
|
||||
credentials {
|
||||
username project.nexusUsername
|
||||
password project.nexusPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
tasks.withType(Javadoc) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
signing
|
||||
id("io.github.patrick.remapper") version "1.4.3"
|
||||
id("com.vanniktech.maven.publish") version "0.36.0"
|
||||
}
|
||||
|
||||
group = "net.dmulloy2"
|
||||
description = "Provides access to the Minecraft protocol"
|
||||
|
||||
val mcVersion: 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") ?: ""
|
||||
val isCI = commitHash.isNotEmpty()
|
||||
|
||||
repositories {
|
||||
if (!isCI) {
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
url = uri("https://repo.codemc.io/repository/nms/")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://hub.spigotmc.org/nexus/content/groups/public/")
|
||||
}
|
||||
|
||||
maven {
|
||||
url = uri("https://libraries.minecraft.net/")
|
||||
metadataSources {
|
||||
mavenPom()
|
||||
artifact()
|
||||
ignoreGradleMetadataRedirection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
compileOnly("commons-lang:commons-lang:2.6")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
test {
|
||||
java.setSrcDirs(emptyList<String>())
|
||||
resources.setSrcDirs(emptyList<String>())
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
dependsOn(":paper:test")
|
||||
}
|
||||
|
||||
check {
|
||||
dependsOn(":paper:check", ":spigot:check")
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
options.release.set(17)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("shadowJar") {
|
||||
group = "build"
|
||||
description = "Builds the Paper and Spigot plugin distributions."
|
||||
dependsOn(":paper:shadowJar", ":spigot:shadowJar")
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral()
|
||||
if (!isSnapshot && !isJitPack) {
|
||||
signAllPublications()
|
||||
}
|
||||
|
||||
coordinates("$group", project.name, "$version")
|
||||
|
||||
pom {
|
||||
name.set(project.name)
|
||||
description.set(project.description)
|
||||
inceptionYear.set("2012")
|
||||
url.set("https://github.com/dmulloy2/ProtocolLib")
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id.set("dmulloy2")
|
||||
name.set("Dan Mulloy")
|
||||
url.set("https://dmulloy2.net/")
|
||||
email.set("dev@dmulloy2.net")
|
||||
}
|
||||
}
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name.set("GNU GENERAL PUBLIC LICENSE - Version 2, June 1991")
|
||||
url.set("https://www.gnu.org/licenses/gpl-2.0.txt")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
|
||||
scm {
|
||||
tag.set("HEAD")
|
||||
url.set("https://github.com/dmulloy2/ProtocolLib")
|
||||
connection.set("scm:git:git://github.com/dmulloy2/ProtocolLib.git")
|
||||
developerConnection.set("scm:git:git@github.com:dmulloy2/ProtocolLib.git")
|
||||
}
|
||||
|
||||
issueManagement {
|
||||
system.set("GitHub Issues")
|
||||
url.set("https://github.com/dmulloy2/ProtocolLib/issues")
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -1,2 +1,7 @@
|
||||
nexusUsername=""
|
||||
nexusPassword=""
|
||||
version=5.5.0-SNAPSHOT
|
||||
mcVersion=26.3
|
||||
mavenCentralUsername=
|
||||
mavenCentralPassword=
|
||||
signing.keyId=
|
||||
signing.password=
|
||||
signing.secretKeyRingFile=
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -170,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -203,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+13
-12
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -14,18 +14,18 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.comphenix.protocol.PacketTypeLookup.ClassLookup;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.scheduler.UniversalRunnable;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.Iterables;
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
/**
|
||||
* Represents the type of a packet in a specific protocol.
|
||||
* <p>
|
||||
@@ -109,130 +109,148 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
|
||||
public static final PacketType BUNDLE = new PacketType(PROTOCOL, SENDER, 0x00, "BundleDelimiter", "Delimiter", "BundleDelimiterPacket");
|
||||
public static final PacketType SPAWN_ENTITY = new PacketType(PROTOCOL, SENDER, 0x01, "AddEntity", "SpawnEntity", "SPacketSpawnObject");
|
||||
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 0x02, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
|
||||
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x03, "Animate", "Animation", "SPacketAnimation");
|
||||
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x04, "AwardStats", "Statistic", "SPacketStatistics");
|
||||
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x05, "BlockChangedAck");
|
||||
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x06, "BlockDestruction", "BlockBreakAnimation", "SPacketBlockBreakAnim");
|
||||
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x07, "BlockEntityData", "TileEntityData", "SPacketUpdateTileEntity");
|
||||
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "BlockEvent", "BlockAction", "SPacketBlockAction");
|
||||
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x09, "BlockUpdate", "BlockChange", "SPacketBlockChange");
|
||||
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x0A, "BossEvent", "Boss", "SPacketUpdateBossInfo");
|
||||
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0B, "ChangeDifficulty", "ServerDifficulty", "SPacketServerDifficulty");
|
||||
public static final PacketType CHUNK_BATCH_FINISHED = new PacketType(PROTOCOL, SENDER, 0x0C, "ChunkBatchFinished");
|
||||
public static final PacketType CHUNK_BATCH_START = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunkBatchStart");
|
||||
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0E, "ChunksBiomes", "ClientboundChunksBiomesPacket");
|
||||
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0F, "ClearTitles");
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x10, "CommandSuggestions", "TabComplete", "SPacketTabComplete");
|
||||
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x11, "Commands");
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
|
||||
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
|
||||
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetData", "WindowData", "SPacketWindowProperty");
|
||||
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x15, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
|
||||
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x16, "CookieRequest");
|
||||
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x17, "Cooldown", "SetCooldown", "SPacketCooldown");
|
||||
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x18, "CustomChatCompletions");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x19, "CustomPayload", "SPacketCustomPayload");
|
||||
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x1A, "DamageEvent", "ClientboundDamageEventPacket");
|
||||
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugSample");
|
||||
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1C, "DeleteChat");
|
||||
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x1D, "Disconnect", "KickDisconnect", "SPacketDisconnect");
|
||||
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x1E, "DisguisedChat");
|
||||
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x1F, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
|
||||
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x20, "Explode", "Explosion", "SPacketExplosion");
|
||||
public static final PacketType UNLOAD_CHUNK = new PacketType(PROTOCOL, SENDER, 0x21, "ForgetLevelChunk", "UnloadChunk", "SPacketUnloadChunk");
|
||||
public static final PacketType GAME_STATE_CHANGE = new PacketType(PROTOCOL, SENDER, 0x22, "GameEvent", "GameStateChange", "SPacketChangeGameState");
|
||||
public static final PacketType OPEN_WINDOW_HORSE = new PacketType(PROTOCOL, SENDER, 0x23, "HorseScreenOpen", "OpenWindowHorse");
|
||||
public static final PacketType HURT_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x24, "HurtAnimation", "ClientboundHurtAnimationPacket");
|
||||
public static final PacketType INITIALIZE_BORDER = new PacketType(PROTOCOL, SENDER, 0x25, "InitializeBorder");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x26, "KeepAlive", "SPacketKeepAlive");
|
||||
public static final PacketType MAP_CHUNK = new PacketType(PROTOCOL, SENDER, 0x27, "LevelChunkWithLight", "MapChunk", "SPacketChunkData");
|
||||
public static final PacketType WORLD_EVENT = new PacketType(PROTOCOL, SENDER, 0x28, "LevelEvent", "WorldEvent", "SPacketEffect");
|
||||
public static final PacketType WORLD_PARTICLES = new PacketType(PROTOCOL, SENDER, 0x29, "LevelParticles", "WorldParticles", "SPacketParticles");
|
||||
public static final PacketType LIGHT_UPDATE = new PacketType(PROTOCOL, SENDER, 0x2A, "LightUpdate");
|
||||
public static final PacketType LOGIN = new PacketType(PROTOCOL, SENDER, 0x2B, "Login", "SPacketJoinGame");
|
||||
public static final PacketType MAP = new PacketType(PROTOCOL, SENDER, 0x2C, "MapItemData", "Map", "SPacketMaps");
|
||||
public static final PacketType OPEN_WINDOW_MERCHANT = new PacketType(PROTOCOL, SENDER, 0x2D, "MerchantOffers", "OpenWindowMerchant");
|
||||
public static final PacketType REL_ENTITY_MOVE = new PacketType(PROTOCOL, SENDER, 0x2E, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "Entity$RelEntityMove", "Entity$PacketPlayOutRelEntityMove");
|
||||
public static final PacketType REL_ENTITY_MOVE_LOOK = new PacketType(PROTOCOL, SENDER, 0x2F, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "Entity$RelEntityMoveLook", "Entity$PacketPlayOutRelEntityMoveLook");
|
||||
public static final PacketType ENTITY_LOOK = new PacketType(PROTOCOL, SENDER, 0x30, "net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "Entity$EntityLook", "Entity$PacketPlayOutEntityLook");
|
||||
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x31, "MoveVehicle", "VehicleMove", "SPacketMoveVehicle");
|
||||
public static final PacketType OPEN_BOOK = new PacketType(PROTOCOL, SENDER, 0x32, "OpenBook");
|
||||
public static final PacketType OPEN_WINDOW = new PacketType(PROTOCOL, SENDER, 0x33, "OpenScreen", "OpenWindow", "SPacketOpenWindow");
|
||||
public static final PacketType OPEN_SIGN_EDITOR = new PacketType(PROTOCOL, SENDER, 0x34, "OpenSignEditor", "SPacketSignEditorOpen");
|
||||
public static final PacketType PING = new PacketType(PROTOCOL, SENDER, 0x35, "Ping");
|
||||
public static final PacketType PONG_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x36, "PongResponse");
|
||||
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x37, "PlaceGhostRecipe", "AutoRecipe", "SPacketPlaceGhostRecipe");
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x38, "PlayerAbilities", "Abilities", "SPacketPlayerAbilities");
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x39, "PlayerChat", "Chat", "SPacketChat");
|
||||
public static final PacketType PLAYER_COMBAT_END = new PacketType(PROTOCOL, SENDER, 0x3A, "PlayerCombatEnd");
|
||||
public static final PacketType PLAYER_COMBAT_ENTER = new PacketType(PROTOCOL, SENDER, 0x3B, "PlayerCombatEnter");
|
||||
public static final PacketType PLAYER_COMBAT_KILL = new PacketType(PROTOCOL, SENDER, 0x3C, "PlayerCombatKill");
|
||||
public static final PacketType PLAYER_INFO_REMOVE = new PacketType(PROTOCOL, SENDER, 0x3D, "PlayerInfoRemove");
|
||||
public static final PacketType PLAYER_INFO = new PacketType(PROTOCOL, SENDER, 0x3E, "PlayerInfoUpdate", "PlayerInfo");
|
||||
public static final PacketType LOOK_AT = new PacketType(PROTOCOL, SENDER, 0x3F, "PlayerLookAt", "LookAt", "SPacketPlayerPosLook");
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x40, "PlayerPosition", "Position");
|
||||
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 0x41, "Recipe", "Recipes", "SPacketRecipeBook");
|
||||
public static final PacketType ENTITY_DESTROY = new PacketType(PROTOCOL, SENDER, 0x42, "RemoveEntities", "EntityDestroy", "SPacketDestroyEntities");
|
||||
public static final PacketType REMOVE_ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x43, "RemoveMobEffect", "RemoveEntityEffect", "SPacketRemoveEntityEffect");
|
||||
public static final PacketType RESET_SCORE = new PacketType(PROTOCOL, SENDER, 0x44, "ResetScore", "ResetScorePacket");
|
||||
public static final PacketType REMOVE_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x45, "ResourcePackPop", "ResourcePackPopPacket");
|
||||
public static final PacketType ADD_RESOURCE_PACK = new PacketType(PROTOCOL, SENDER, 0x46, "ResourcePackPush", "ResourcePackPushPacket");
|
||||
public static final PacketType RESPAWN = new PacketType(PROTOCOL, SENDER, 0x47, "Respawn", "SPacketRespawn");
|
||||
public static final PacketType ENTITY_HEAD_ROTATION = new PacketType(PROTOCOL, SENDER, 0x48, "RotateHead", "EntityHeadRotation", "SPacketEntityHeadLook");
|
||||
public static final PacketType MULTI_BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x49, "SectionBlocksUpdate", "MultiBlockChange", "SPacketMultiBlockChange");
|
||||
public static final PacketType SELECT_ADVANCEMENT_TAB = new PacketType(PROTOCOL, SENDER, 0x4A, "SelectAdvancementsTab", "SelectAdvancementTab", "SPacketSelectAdvancementsTab");
|
||||
public static final PacketType SERVER_DATA = new PacketType(PROTOCOL, SENDER, 0x4B, "ServerData");
|
||||
public static final PacketType SET_ACTION_BAR_TEXT = new PacketType(PROTOCOL, SENDER, 0x4C, "SetActionBarText");
|
||||
public static final PacketType SET_BORDER_CENTER = new PacketType(PROTOCOL, SENDER, 0x4D, "SetBorderCenter");
|
||||
public static final PacketType SET_BORDER_LERP_SIZE = new PacketType(PROTOCOL, SENDER, 0x4E, "SetBorderLerpSize");
|
||||
public static final PacketType SET_BORDER_SIZE = new PacketType(PROTOCOL, SENDER, 0x4F, "SetBorderSize");
|
||||
public static final PacketType SET_BORDER_WARNING_DELAY = new PacketType(PROTOCOL, SENDER, 0x50, "SetBorderWarningDelay");
|
||||
public static final PacketType SET_BORDER_WARNING_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x51, "SetBorderWarningDistance");
|
||||
public static final PacketType CAMERA = new PacketType(PROTOCOL, SENDER, 0x52, "SetCamera", "Camera", "SPacketCamera");
|
||||
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x53, "SetCarriedItem", "HeldItemSlot", "SPacketHeldItemChange");
|
||||
public static final PacketType VIEW_CENTRE = new PacketType(PROTOCOL, SENDER, 0x54, "SetChunkCacheCenter", "ViewCentre");
|
||||
public static final PacketType VIEW_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x55, "SetChunkCacheRadius", "ViewDistance");
|
||||
public static final PacketType SPAWN_POSITION = new PacketType(PROTOCOL, SENDER, 0x56, "SetDefaultSpawnPosition", "SpawnPosition", "SPacketSpawnPosition");
|
||||
public static final PacketType SCOREBOARD_DISPLAY_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x57, "SetDisplayObjective", "ScoreboardDisplayObjective", "SPacketDisplayObjective");
|
||||
public static final PacketType ENTITY_METADATA = new PacketType(PROTOCOL, SENDER, 0x58, "SetEntityData", "EntityMetadata", "SPacketEntityMetadata");
|
||||
public static final PacketType ATTACH_ENTITY = new PacketType(PROTOCOL, SENDER, 0x59, "SetEntityLink", "AttachEntity", "SPacketEntityAttach");
|
||||
public static final PacketType ENTITY_VELOCITY = new PacketType(PROTOCOL, SENDER, 0x5A, "SetEntityMotion", "EntityVelocity", "SPacketEntityVelocity");
|
||||
public static final PacketType ENTITY_EQUIPMENT = new PacketType(PROTOCOL, SENDER, 0x5B, "SetEquipment", "EntityEquipment", "SPacketEntityEquipment");
|
||||
public static final PacketType EXPERIENCE = new PacketType(PROTOCOL, SENDER, 0x5C, "SetExperience", "Experience", "SPacketSetExperience");
|
||||
public static final PacketType UPDATE_HEALTH = new PacketType(PROTOCOL, SENDER, 0x5D, "SetHealth", "UpdateHealth", "SPacketUpdateHealth");
|
||||
public static final PacketType SCOREBOARD_OBJECTIVE = new PacketType(PROTOCOL, SENDER, 0x5E, "SetObjective", "ScoreboardObjective", "SPacketScoreboardObjective");
|
||||
public static final PacketType MOUNT = new PacketType(PROTOCOL, SENDER, 0x5F, "SetPassengers", "Mount", "SPacketSetPassengers");
|
||||
public static final PacketType SCOREBOARD_TEAM = new PacketType(PROTOCOL, SENDER, 0x60, "SetPlayerTeam", "ScoreboardTeam", "SPacketTeams");
|
||||
public static final PacketType SCOREBOARD_SCORE = new PacketType(PROTOCOL, SENDER, 0x61, "SetScore", "ScoreboardScore", "SPacketUpdateScore");
|
||||
public static final PacketType UPDATE_SIMULATION_DISTANCE = new PacketType(PROTOCOL, SENDER, 0x62, "SetSimulationDistance");
|
||||
public static final PacketType SET_SUBTITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x63, "SetSubtitleText");
|
||||
public static final PacketType UPDATE_TIME = new PacketType(PROTOCOL, SENDER, 0x64, "SetTime", "UpdateTime", "SPacketTimeUpdate");
|
||||
public static final PacketType SET_TITLE_TEXT = new PacketType(PROTOCOL, SENDER, 0x65, "SetTitleText");
|
||||
public static final PacketType SET_TITLES_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x66, "SetTitlesAnimation");
|
||||
public static final PacketType ENTITY_SOUND = new PacketType(PROTOCOL, SENDER, 0x67, "SoundEntity", "EntitySound", "SPacketSoundEffect");
|
||||
public static final PacketType NAMED_SOUND_EFFECT = new PacketType(PROTOCOL, SENDER, 0x68, "Sound", "NamedSoundEffect");
|
||||
public static final PacketType START_CONFIGURATION = new PacketType(PROTOCOL, SENDER, 0x69, "StartConfiguration");
|
||||
public static final PacketType STOP_SOUND = new PacketType(PROTOCOL, SENDER, 0x6A, "StopSound");
|
||||
public static final PacketType STORE_COOKIE = new PacketType(PROTOCOL, SENDER, 0x6B, "StoreCookie");
|
||||
public static final PacketType SYSTEM_CHAT = new PacketType(PROTOCOL, SENDER, 0x6C, "SystemChat");
|
||||
public static final PacketType PLAYER_LIST_HEADER_FOOTER = new PacketType(PROTOCOL, SENDER, 0x6D, "TabList", "PlayerListHeaderFooter", "SPacketPlayerListHeaderFooter");
|
||||
public static final PacketType NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x6E, "TagQuery", "NBTQuery");
|
||||
public static final PacketType COLLECT = new PacketType(PROTOCOL, SENDER, 0x6F, "TakeItemEntity", "Collect", "SPacketCollectItem");
|
||||
public static final PacketType ENTITY_TELEPORT = new PacketType(PROTOCOL, SENDER, 0x70, "TeleportEntity", "EntityTeleport", "SPacketEntityTeleport");
|
||||
public static final PacketType TICKING_STATE = new PacketType(PROTOCOL, SENDER, 0x71, "TickingState", "TickingStatePacket");
|
||||
public static final PacketType TICKING_STEP_STATE = new PacketType(PROTOCOL, SENDER, 0x72, "TickingStep", "TickingStepPacket");
|
||||
public static final PacketType TRANSFER = new PacketType(PROTOCOL, SENDER, 0x73, "Transfer");
|
||||
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x74, "UpdateAdvancements", "Advancements", "SPacketAdvancementInfo");
|
||||
public static final PacketType UPDATE_ATTRIBUTES = new PacketType(PROTOCOL, SENDER, 0x75, "UpdateAttributes", "SPacketEntityProperties");
|
||||
public static final PacketType ENTITY_EFFECT = new PacketType(PROTOCOL, SENDER, 0x76, "UpdateMobEffect", "EntityEffect", "SPacketEntityEffect");
|
||||
public static final PacketType RECIPE_UPDATE = new PacketType(PROTOCOL, SENDER, 0x77, "UpdateRecipes", "RecipeUpdate");
|
||||
public static final PacketType TAGS = new PacketType(PROTOCOL, SENDER, 0x78, "UpdateTags", "Tags");
|
||||
public static final PacketType PROJECTILE_POWER = new PacketType(PROTOCOL, SENDER, 0x79, "ProjectilePower");
|
||||
public static final PacketType REPORT_DETAILS = new PacketType(PROTOCOL, SENDER, 0x7A, "CustomReportDetails");
|
||||
public static final PacketType SERVER_LINKS = new PacketType(PROTOCOL, SENDER, 0x7B, "ServerLinks");
|
||||
|
||||
// ---- Removed in 1.9
|
||||
public static final PacketType ANIMATION = new PacketType(PROTOCOL, SENDER, 0x02, "Animate", "Animation", "SPacketAnimation");
|
||||
public static final PacketType STATISTIC = new PacketType(PROTOCOL, SENDER, 0x03, "AwardStats", "Statistic", "SPacketStatistics");
|
||||
public static final PacketType BLOCK_CHANGED_ACK = new PacketType(PROTOCOL, SENDER, 0x04, "BlockChangedAck");
|
||||
public static final PacketType BLOCK_BREAK_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x05, "BlockDestruction", "BlockBreakAnimation", "SPacketBlockBreakAnim");
|
||||
public static final PacketType TILE_ENTITY_DATA = new PacketType(PROTOCOL, SENDER, 0x06, "BlockEntityData", "TileEntityData", "SPacketUpdateTileEntity");
|
||||
public static final PacketType BLOCK_ACTION = new PacketType(PROTOCOL, SENDER, 0x07, "BlockEvent", "BlockAction", "SPacketBlockAction");
|
||||
public static final PacketType BLOCK_CHANGE = new PacketType(PROTOCOL, SENDER, 0x08, "BlockUpdate", "BlockChange", "SPacketBlockChange");
|
||||
public static final PacketType BOSS = new PacketType(PROTOCOL, SENDER, 0x09, "BossEvent", "Boss", "SPacketUpdateBossInfo");
|
||||
public static final PacketType SERVER_DIFFICULTY = new PacketType(PROTOCOL, SENDER, 0x0A, "ChangeDifficulty", "ServerDifficulty", "SPacketServerDifficulty");
|
||||
public static final PacketType CHUNK_BATCH_FINISHED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchFinished");
|
||||
public static final PacketType CHUNK_BATCH_START = new PacketType(PROTOCOL, SENDER, 0x0C, "ChunkBatchStart");
|
||||
public static final PacketType CHUNKS_BIOMES = new PacketType(PROTOCOL, SENDER, 0x0D, "ChunksBiomes", "ClientboundChunksBiomesPacket");
|
||||
public static final PacketType CLEAR_TITLES = new PacketType(PROTOCOL, SENDER, 0x0E, "ClearTitles");
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestions", "TabComplete", "SPacketTabComplete");
|
||||
public static final PacketType COMMANDS = new PacketType(PROTOCOL, SENDER, 0x10, "Commands");
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerClose", "CloseWindow", "SPacketCloseWindow");
|
||||
public static final PacketType WINDOW_ITEMS = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerSetContent", "WindowItems", "SPacketWindowItems");
|
||||
public static final PacketType WINDOW_DATA = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerSetData", "WindowData", "SPacketWindowProperty");
|
||||
public static final PacketType SET_SLOT = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSetSlot", "SetSlot", "SPacketSetSlot");
|
||||
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x15, "CookieRequest");
|
||||
public static final PacketType SET_COOLDOWN = new PacketType(PROTOCOL, SENDER, 0x16, "Cooldown", "SetCooldown", "SPacketCooldown");
|
||||
public static final PacketType CUSTOM_CHAT_COMPLETIONS = new PacketType(PROTOCOL, SENDER, 0x17, "CustomChatCompletions");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x18, "CustomPayload", "SPacketCustomPayload");
|
||||
public static final PacketType DAMAGE_EVENT = new PacketType(PROTOCOL, SENDER, 0x19, "DamageEvent", "ClientboundDamageEventPacket");
|
||||
public static final PacketType DEBUG_BLOCK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1A, "DebugBlockValue");
|
||||
public static final PacketType DEBUG_CHUNK_VALUE = new PacketType(PROTOCOL, SENDER, 0x1B, "DebugChunkValue");
|
||||
public static final PacketType DEBUG_ENTITY_VALUE = new PacketType(PROTOCOL, SENDER, 0x1C, "DebugEntityValue");
|
||||
public static final PacketType DEBUG_EVENT = new PacketType(PROTOCOL, SENDER, 0x1D, "DebugEvent");
|
||||
public static final PacketType DEBUG_SAMPLE = new PacketType(PROTOCOL, SENDER, 0x1E, "DebugSample");
|
||||
public static final PacketType DELETE_CHAT_MESSAGE = new PacketType(PROTOCOL, SENDER, 0x1F, "DeleteChat");
|
||||
public static final PacketType KICK_DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x20, "Disconnect", "KickDisconnect", "SPacketDisconnect");
|
||||
public static final PacketType DISGUISED_CHAT = new PacketType(PROTOCOL, SENDER, 0x21, "DisguisedChat");
|
||||
public static final PacketType ENTITY_STATUS = new PacketType(PROTOCOL, SENDER, 0x22, "EntityEvent", "EntityStatus", "SPacketEntityStatus");
|
||||
public static final PacketType ENTITY_POSITION_SYNC = new PacketType(PROTOCOL, SENDER, 0x23, "EntityPositionSync");
|
||||
public static final PacketType EXPLOSION = new PacketType(PROTOCOL, SENDER, 0x24, "Explode", "Explosion", "SPacketExplosion");
|
||||
public static final PacketType 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
|
||||
@@ -252,8 +270,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
@Deprecated
|
||||
public static final PacketType UPDATE_ENTITY_NBT = new PacketType(PROTOCOL, SENDER, 253, "UpdateEntityNBT");
|
||||
|
||||
// ----- Renamed packets
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to {@link #WINDOW_DATA}
|
||||
*/
|
||||
@@ -278,8 +294,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
@Deprecated
|
||||
public static final PacketType OPEN_SIGN_ENTITY = OPEN_SIGN_EDITOR.clone();
|
||||
|
||||
// ----- Replaced in 1.9.4
|
||||
|
||||
/**
|
||||
* @deprecated Replaced by {@link #TILE_ENTITY_DATA}
|
||||
*/
|
||||
@@ -287,8 +301,6 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static final PacketType UPDATE_SIGN = MinecraftReflection.signUpdateExists() ? new PacketType(PROTOCOL, SENDER, 252, "UpdateSign") :
|
||||
TILE_ENTITY_DATA.clone();
|
||||
|
||||
// ---- Removed in 1.14
|
||||
|
||||
/**
|
||||
* @deprecated Removed in 1.14
|
||||
*/
|
||||
@@ -403,6 +415,18 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
@Deprecated
|
||||
public static final PacketType RESOURCE_PACK_SEND = new PacketType(PROTOCOL, SENDER, 234, "ResourcePackSend", "SPacketResourcePackSend");
|
||||
|
||||
/**
|
||||
* @deprecated Removed in 1.21.2: replaced with individual packets for adding/removing recipes
|
||||
*/
|
||||
@Deprecated
|
||||
public static final PacketType RECIPES = new PacketType(PROTOCOL, SENDER, 233, "Recipe", "Recipes", "SPacketRecipeBook");
|
||||
|
||||
/**
|
||||
* @deprecated Removed in 1.21.5; replace by SPAWN_ENTITY
|
||||
*/
|
||||
@Deprecated
|
||||
public static final PacketType SPAWN_ENTITY_EXPERIENCE_ORB = new PacketType(PROTOCOL, SENDER, 232, "AddExperienceOrb", "SpawnEntityExperienceOrb", "SPacketSpawnExperienceOrb");
|
||||
|
||||
private static final Server INSTANCE = new Server();
|
||||
|
||||
// Prevent accidental construction
|
||||
@@ -424,63 +448,74 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
private static final Sender SENDER = Sender.CLIENT;
|
||||
|
||||
public static final PacketType TELEPORT_ACCEPT = new PacketType(PROTOCOL, SENDER, 0x00, "AcceptTeleportation", "TeleportAccept", "CPacketConfirmTeleport");
|
||||
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x01, "BlockEntityTagQuery", "TileNBTQuery");
|
||||
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x02, "ChangeDifficulty", "DifficultyChange");
|
||||
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x03, "ChatAck");
|
||||
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x04, "ChatCommand");
|
||||
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x05, "ChatCommandSigned");
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x06, "Chat", "CPacketChatMessage");
|
||||
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x07, "ChatSessionUpdate");
|
||||
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x08, "ChunkBatchReceived");
|
||||
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x09, "ClientCommand", "CPacketClientStatus");
|
||||
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0A, "ClientInformation", "Settings", "CPacketClientSettings");
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0B, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
|
||||
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x0C, "ConfigurationAcknowledged");
|
||||
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x0D, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
|
||||
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x0E, "ContainerClick", "WindowClick", "CPacketClickWindow");
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x0F, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
|
||||
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x10, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
|
||||
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x11, "CookieResponse");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x12, "CustomPayload", "CPacketCustomPayload");
|
||||
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x13, "DebugSampleSubscription");
|
||||
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x14, "EditBook", "BEdit");
|
||||
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x15, "EntityTagQuery", "EntityNBTQuery");
|
||||
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x16, "Interact", "UseEntity", "CPacketUseEntity");
|
||||
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x17, "JigsawGenerate");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x18, "KeepAlive", "CPacketKeepAlive");
|
||||
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x19, "LockDifficulty", "DifficultyLock");
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1A, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
|
||||
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1B, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
|
||||
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x1C, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
|
||||
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x1D, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
|
||||
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x1E, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
|
||||
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x1F, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
|
||||
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x20, "PickItem");
|
||||
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x21, "PingRequest");
|
||||
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x22, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x23, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
|
||||
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x24, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
|
||||
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x25, "PlayerCommand", "EntityAction", "CPacketEntityAction");
|
||||
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x26, "PlayerInput", "SteerVehicle", "CPacketInput");
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x27, "Pong");
|
||||
public static final PacketType RECIPE_SETTINGS = new PacketType(PROTOCOL, SENDER, 0x28, "RecipeBookChangeSettings", "RecipeSettings");
|
||||
public static final PacketType RECIPE_DISPLAYED = new PacketType(PROTOCOL, SENDER, 0x29, "RecipeBookSeenRecipe", "RecipeDisplayed", "CPacketRecipeInfo");
|
||||
public static final PacketType ITEM_NAME = new PacketType(PROTOCOL, SENDER, 0x2A, "RenameItem", "ItemName");
|
||||
public static final PacketType RESOURCE_PACK_STATUS = new PacketType(PROTOCOL, SENDER, 0x2B, "ResourcePack", "ResourcePackStatus", "CPacketResourcePackStatus");
|
||||
public static final PacketType ADVANCEMENTS = new PacketType(PROTOCOL, SENDER, 0x2C, "SeenAdvancements", "Advancements", "CPacketSeenAdvancements");
|
||||
public static final PacketType TR_SEL = new PacketType(PROTOCOL, SENDER, 0x2D, "SelectTrade", "TrSel");
|
||||
public static final PacketType BEACON = new PacketType(PROTOCOL, SENDER, 0x2E, "SetBeacon", "Beacon");
|
||||
public static final PacketType HELD_ITEM_SLOT = new PacketType(PROTOCOL, SENDER, 0x2F, "SetCarriedItem", "HeldItemSlot", "CPacketHeldItemChange");
|
||||
public static final PacketType SET_COMMAND_BLOCK = new PacketType(PROTOCOL, SENDER, 0x30, "SetCommandBlock");
|
||||
public static final PacketType SET_COMMAND_MINECART = new PacketType(PROTOCOL, SENDER, 0x31, "SetCommandMinecart");
|
||||
public static final PacketType SET_CREATIVE_SLOT = new PacketType(PROTOCOL, SENDER, 0x32, "SetCreativeModeSlot", "SetCreativeSlot", "CPacketCreativeInventoryAction");
|
||||
public static final PacketType SET_JIGSAW = new PacketType(PROTOCOL, SENDER, 0x33, "SetJigsawBlock", "SetJigsaw");
|
||||
public static final PacketType STRUCT = new PacketType(PROTOCOL, SENDER, 0x34, "SetStructureBlock", "Struct");
|
||||
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x35, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
|
||||
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x36, "Swing", "ArmAnimation", "CPacketAnimation");
|
||||
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x37, "TeleportToEntity", "Spectate", "CPacketSpectate");
|
||||
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
|
||||
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem", "CPacketPlayerTryUseItem");
|
||||
public static final PacketType ATTACK = new PacketType(PROTOCOL, SENDER, 0x01, "Attack");
|
||||
public static final PacketType TILE_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x02, "BlockEntityTagQuery", "TileNBTQuery");
|
||||
public static final PacketType SELECT_BUNDLE_ITEM = new PacketType(PROTOCOL, SENDER, 0x03, "SelectBundleItem");
|
||||
public static final PacketType DIFFICULTY_CHANGE = new PacketType(PROTOCOL, SENDER, 0x04, "ChangeDifficulty", "DifficultyChange");
|
||||
public static final PacketType CHANGE_GAME_MODE = new PacketType(PROTOCOL, SENDER, 0x05, "ChangeGameMode");
|
||||
public static final PacketType CHAT_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ChatAck");
|
||||
public static final PacketType CHAT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x07, "ChatCommand");
|
||||
public static final PacketType CHAT_COMMAND_SIGNED = new PacketType(PROTOCOL, SENDER, 0x08, "ChatCommandSigned");
|
||||
public static final PacketType CHAT = new PacketType(PROTOCOL, SENDER, 0x09, "Chat", "CPacketChatMessage");
|
||||
public static final PacketType CHAT_SESSION_UPDATE = new PacketType(PROTOCOL, SENDER, 0x0A, "ChatSessionUpdate");
|
||||
public static final PacketType CHUNK_BATCH_RECEIVED = new PacketType(PROTOCOL, SENDER, 0x0B, "ChunkBatchReceived");
|
||||
public static final PacketType CLIENT_COMMAND = new PacketType(PROTOCOL, SENDER, 0x0C, "ClientCommand", "CPacketClientStatus");
|
||||
public static final PacketType CLIENT_TICK_END = new PacketType(PROTOCOL, SENDER, 0x0D, "ClientTickEnd");
|
||||
public static final PacketType SETTINGS = new PacketType(PROTOCOL, SENDER, 0x0E, "ClientInformation", "Settings", "CPacketClientSettings");
|
||||
public static final PacketType TAB_COMPLETE = new PacketType(PROTOCOL, SENDER, 0x0F, "CommandSuggestion", "TabComplete", "CPacketTabComplete");
|
||||
public static final PacketType CONFIGURATION_ACK = new PacketType(PROTOCOL, SENDER, 0x10, "ConfigurationAcknowledged");
|
||||
public static final PacketType ENCHANT_ITEM = new PacketType(PROTOCOL, SENDER, 0x11, "ContainerButtonClick", "EnchantItem", "CPacketEnchantItem");
|
||||
public static final PacketType WINDOW_CLICK = new PacketType(PROTOCOL, SENDER, 0x12, "ContainerClick", "WindowClick", "CPacketClickWindow");
|
||||
public static final PacketType CLOSE_WINDOW = new PacketType(PROTOCOL, SENDER, 0x13, "ContainerClose", "CloseWindow", "CPacketCloseWindow");
|
||||
public static final PacketType CONTAINER_SLOT_STATE_CHANGED = new PacketType(PROTOCOL, SENDER, 0x14, "ContainerSlotStateChanged", "ContainerSlotStateChangedPacket");
|
||||
public static final PacketType COOKIE_RESPONSE = new PacketType(PROTOCOL, SENDER, 0x15, "CookieResponse");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x16, "CustomPayload", "CPacketCustomPayload");
|
||||
public static final PacketType DEBUG_SUBSCRIPTION_REQUEST = new PacketType(PROTOCOL, SENDER, 0x17, "DebugSubscriptionRequest");
|
||||
public static final PacketType B_EDIT = new PacketType(PROTOCOL, SENDER, 0x18, "EditBook", "BEdit");
|
||||
public static final PacketType ENTITY_NBT_QUERY = new PacketType(PROTOCOL, SENDER, 0x19, "EntityTagQuery", "EntityNBTQuery");
|
||||
public static final PacketType USE_ENTITY = new PacketType(PROTOCOL, SENDER, 0x1A, "Interact", "UseEntity", "CPacketUseEntity");
|
||||
public static final PacketType JIGSAW_GENERATE = new PacketType(PROTOCOL, SENDER, 0x1B, "JigsawGenerate");
|
||||
public static final PacketType KEEP_ALIVE = new PacketType(PROTOCOL, SENDER, 0x1C, "KeepAlive", "CPacketKeepAlive");
|
||||
public static final PacketType DIFFICULTY_LOCK = new PacketType(PROTOCOL, SENDER, 0x1D, "LockDifficulty", "DifficultyLock");
|
||||
public static final PacketType POSITION = new PacketType(PROTOCOL, SENDER, 0x1E, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "Flying$Position", "Flying$PacketPlayInPosition", "CPacketPlayer$Position");
|
||||
public static final PacketType POSITION_LOOK = new PacketType(PROTOCOL, SENDER, 0x1F, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "Flying$PositionLook", "Flying$PacketPlayInPositionLook", "CPacketPlayer$PositionRotation");
|
||||
public static final PacketType LOOK = new PacketType(PROTOCOL, SENDER, 0x20, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "Flying$Look", "Flying$PacketPlayInLook", "CPacketPlayer$Rotation");
|
||||
public static final PacketType GROUND = new PacketType(PROTOCOL, SENDER, 0x21, "net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$StatusOnly", "Flying$d");
|
||||
public static final PacketType VEHICLE_MOVE = new PacketType(PROTOCOL, SENDER, 0x22, "MoveVehicle", "VehicleMove", "CPacketVehicleMove");
|
||||
public static final PacketType BOAT_MOVE = new PacketType(PROTOCOL, SENDER, 0x23, "PaddleBoat", "BoatMove", "CPacketSteerBoat");
|
||||
public static final PacketType PICK_ITEM_FROM_BLOCK = new PacketType(PROTOCOL, SENDER, 0x24, "PickItemFromBlock");
|
||||
public static final PacketType PICK_ITEM = new PacketType(PROTOCOL, SENDER, 0x25, "PickItemFromEntity", "PickItem");
|
||||
public static final PacketType PING_REQUEST = new PacketType(PROTOCOL, SENDER, 0x26, "PingRequest");
|
||||
public static final PacketType AUTO_RECIPE = new PacketType(PROTOCOL, SENDER, 0x27, "PlaceRecipe", "AutoRecipe", "CPacketPlaceRecipe");
|
||||
public static final PacketType ABILITIES = new PacketType(PROTOCOL, SENDER, 0x28, "PlayerAbilities", "Abilities", "CPacketPlayerAbilities");
|
||||
public static final PacketType BLOCK_DIG = new PacketType(PROTOCOL, SENDER, 0x29, "PlayerAction", "BlockDig", "CPacketPlayerDigging");
|
||||
public static final PacketType ENTITY_ACTION = new PacketType(PROTOCOL, SENDER, 0x2A, "PlayerCommand", "EntityAction", "CPacketEntityAction");
|
||||
public static final PacketType STEER_VEHICLE = new PacketType(PROTOCOL, SENDER, 0x2B, "PlayerInput", "SteerVehicle", "CPacketInput");
|
||||
public static final PacketType PLAYER_LOADED = new PacketType(PROTOCOL, SENDER, 0x2C, "PlayerLoaded");
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x2D, "Pong");
|
||||
public static final PacketType 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");
|
||||
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x43, "UseItem", "CPacketPlayerTryUseItem");
|
||||
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x44, "CustomClickAction");
|
||||
|
||||
/**
|
||||
* @deprecated Removed in 1.17
|
||||
@@ -500,6 +535,18 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
@Deprecated
|
||||
public static final PacketType CHAT_PREVIEW = new PacketType(PROTOCOL, SENDER, 253, "ChatPreview");
|
||||
|
||||
/**
|
||||
* deprecated Removed in 1.21.9
|
||||
*/
|
||||
@Deprecated
|
||||
public static final PacketType DEBUG_SAMPLE_SUBSCRIPTION = new PacketType(PROTOCOL, SENDER, 0x16, "DebugSampleSubscription");
|
||||
|
||||
/**
|
||||
* @deprecated 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
|
||||
*/
|
||||
@@ -609,7 +656,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
@ForceAsync
|
||||
public static final PacketType DISCONNECT = new PacketType(PROTOCOL, SENDER, 0x00, "LoginDisconnect", "Disconnect", "SPacketDisconnect");
|
||||
public static final PacketType ENCRYPTION_BEGIN = new PacketType(PROTOCOL, SENDER, 0x01, "Hello", "EncryptionBegin", "SPacketEncryptionRequest");
|
||||
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "GameProfile", "Success", "SPacketLoginSuccess");
|
||||
public static final PacketType SUCCESS = new PacketType(PROTOCOL, SENDER, 0x02, "LoginFinished", "GameProfile", "Success", "SPacketLoginSuccess");
|
||||
public static final PacketType SET_COMPRESSION = new PacketType(PROTOCOL, SENDER, 0x03, "LoginCompression", "SetCompression", "SPacketEnableCompression");
|
||||
public static final PacketType CUSTOM_PAYLOAD = new PacketType(PROTOCOL, SENDER, 0x04, "CustomQuery", "CustomPayload", "SPacketCustomPayload");
|
||||
public static final PacketType COOKIE_REQUEST = new PacketType(PROTOCOL, SENDER, 0x05, "CookieRequest");
|
||||
@@ -681,13 +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 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
|
||||
@@ -719,6 +770,8 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
|
||||
public static final PacketType PONG = new PacketType(PROTOCOL, SENDER, 0x05, "Pong");
|
||||
public static final PacketType RESOURCE_PACK_ACK = new PacketType(PROTOCOL, SENDER, 0x06, "ResourcePack");
|
||||
public static final PacketType SELECT_KNOWN_PACKS = new PacketType(PROTOCOL, SENDER, 0x07, "ServerboundSelectKnownPacks");
|
||||
public static final PacketType CUSTOM_CLICK_ACTION = new PacketType(PROTOCOL, SENDER, 0x08, "CustomClickAction");
|
||||
public static final PacketType ACCEPT_CODE_OF_CONDUCT = new PacketType(PROTOCOL, SENDER, 0x09, "AcceptCodeOfConduct");
|
||||
|
||||
private static final Client INSTANCE = new Client();
|
||||
|
||||
|
||||
@@ -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));
|
||||
@@ -504,6 +498,17 @@ public class ProtocolLib extends JavaPlugin {
|
||||
}
|
||||
|
||||
private void checkUpdates() {
|
||||
// Do not run updater if no supported updater is loaded
|
||||
if (this.updater == null) {
|
||||
highlyVisibleError(
|
||||
" WARNING ",
|
||||
" ProtocolLib is unable to check for updates. ",
|
||||
" You are likely not using a supported version (Such as CraftBukkit). "
|
||||
);
|
||||
ProtocolLibrary.disableUpdates();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore milliseconds - it's pointless
|
||||
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ public class ProtocolLibrary {
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "1.21.1";
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "26.3";
|
||||
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) 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 = "2024-08-08";
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.scheduler.ProtocolScheduler;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -274,7 +275,7 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private boolean onMainThread() {
|
||||
return Thread.currentThread().getId() == mainThread.getId();
|
||||
return Thread.currentThread() == mainThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -308,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);
|
||||
@@ -322,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,8 +346,8 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
* Construct a asynchronous marker with all the default values.
|
||||
* @return Asynchronous marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker() {
|
||||
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
public AsyncMarker createAsyncMarker(Injector injector) {
|
||||
return createAsyncMarker(injector, AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,13 +355,13 @@ public class AsyncFilterManager implements AsynchronousManager {
|
||||
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||
* @return An async marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker(long timeoutDelta) {
|
||||
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
|
||||
public AsyncMarker createAsyncMarker(Injector injector, long timeoutDelta) {
|
||||
return createAsyncMarker(injector, timeoutDelta, currentSendingIndex.incrementAndGet());
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
|
||||
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
|
||||
private AsyncMarker createAsyncMarker(Injector injector, long timeoutDelta, long sendingIndex) {
|
||||
return new AsyncMarker(injector, sendingIndex, System.currentTimeMillis(), timeoutDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -538,7 +538,7 @@ public class AsyncListenerHandler {
|
||||
*/
|
||||
private void listenerLoop(int workerID) {
|
||||
// Danger, danger!
|
||||
if (Thread.currentThread().getId() == mainThread.getId())
|
||||
if (Thread.currentThread() == mainThread)
|
||||
throw new IllegalStateException("Do not call this method from the main thread.");
|
||||
if (cancelled)
|
||||
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||
|
||||
@@ -23,14 +23,18 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketStream;
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
@@ -64,7 +68,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
/**
|
||||
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||
*/
|
||||
private transient PacketStream packetStream;
|
||||
private final Injector injector;
|
||||
|
||||
/**
|
||||
* Current list of async packet listeners.
|
||||
@@ -86,7 +90,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
private volatile boolean processed;
|
||||
|
||||
// Whether or not the packet has been sent
|
||||
private volatile boolean transmitted;
|
||||
private final AtomicBoolean transmitted = new AtomicBoolean(false);
|
||||
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
@@ -109,11 +113,8 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
* Create a container for asyncronous packets.
|
||||
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
|
||||
if (packetStream == null)
|
||||
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||
|
||||
this.packetStream = packetStream;
|
||||
AsyncMarker(Injector injector, long sendingIndex, long initialTime, long timeoutDelta) {
|
||||
this.injector = Objects.requireNonNull(injector, "injector is nul");
|
||||
|
||||
// Timeout
|
||||
this.initialTime = initialTime;
|
||||
@@ -180,16 +181,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
* Retrieve the packet stream responsible for transmitting this packet.
|
||||
* @return The packet stream.
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketStream getPacketStream() {
|
||||
return packetStream;
|
||||
return ProtocolLibrary.getProtocolManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output packet stream responsible for transmitting this packet.
|
||||
* @param packetStream - new output packet stream.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setPacketStream(PacketStream packetStream) {
|
||||
this.packetStream = packetStream;
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,7 +288,7 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||
*/
|
||||
public boolean isTransmitted() {
|
||||
return transmitted;
|
||||
return transmitted.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,13 +386,18 @@ public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
* @throws IOException If the packet couldn't be sent.
|
||||
*/
|
||||
void sendPacket(PacketEvent event) throws IOException {
|
||||
if (event.isServerPacket()) {
|
||||
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
} else {
|
||||
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
|
||||
false);
|
||||
if (!this.transmitted.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object handle = event.getPacket().getHandle();
|
||||
|
||||
if (event.isServerPacket()) {
|
||||
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
||||
this.injector.sendClientboundPacket(handle, marker, false);
|
||||
} else {
|
||||
this.injector.readServerboundPacket(handle);
|
||||
}
|
||||
transmitted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,8 @@ import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.MinMaxPriorityQueue;
|
||||
|
||||
@@ -209,7 +211,7 @@ class PacketProcessingQueue {
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (AsyncListenerHandler handler : map.values()) {
|
||||
for (AsyncListenerHandler handler : ImmutableList.copyOf(map.values())) {
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ import com.comphenix.protocol.wrappers.WrappedLevelChunkData;
|
||||
import com.comphenix.protocol.wrappers.WrappedMessageSignature;
|
||||
import com.comphenix.protocol.wrappers.WrappedNumberFormat;
|
||||
import com.comphenix.protocol.wrappers.WrappedParticle;
|
||||
import com.comphenix.protocol.wrappers.WrappedPositionMoveRotation;
|
||||
import com.comphenix.protocol.wrappers.WrappedPositionPath;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import com.comphenix.protocol.wrappers.WrappedRegistrable;
|
||||
@@ -64,6 +66,7 @@ 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.nbt.NbtBase;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
@@ -597,9 +600,7 @@ public abstract class AbstractStructure {
|
||||
*/
|
||||
public StructureModifier<List<PlayerInfoData>> getPlayerInfoDataLists() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(PlayerInfoData.getConverter()));
|
||||
return getLists(PlayerInfoData.getConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1165,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()
|
||||
@@ -1297,6 +1324,48 @@ 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} (before 26.3).
|
||||
*
|
||||
* @return A modifier for PositionMoveRotation fields.
|
||||
*/
|
||||
public StructureModifier<WrappedPositionMoveRotation> getPositionMoveRotation() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getPositionMoveRotationClass(),
|
||||
WrappedPositionMoveRotation.getConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -232,13 +232,13 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifications of received packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
|
||||
throw new IllegalStateException("Override onPacketSending to get notifications of sent packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.comphenix.protocol.injector.PluginVerifier.VerificationResult;
|
||||
import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
|
||||
import com.comphenix.protocol.injector.collection.PacketListenerSet;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
@@ -151,29 +152,20 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
|
||||
if (!this.closed) {
|
||||
// if we skip the packet events later when actually writing into the pipeline we at least notify all
|
||||
// monitor listeners before doing so - they will not be able to change the event tho
|
||||
if (!filters) {
|
||||
// ensure we are on the main thread if any listener requires that
|
||||
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
|
||||
NetworkMarker copy = marker; // okay fine
|
||||
ProtocolLibrary.getScheduler().scheduleSyncDelayedTask(
|
||||
() -> this.sendServerPacket(receiver, packet, copy, false), 1L);
|
||||
return;
|
||||
}
|
||||
|
||||
// construct the event and post to all monitor listeners
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A monitor listener should never modify a packet/event so we can simply invoke monitor listeners
|
||||
// independently of our injector pipeline
|
||||
if (!filters) {
|
||||
this.runMonitorListeners(packet, () -> {
|
||||
PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false);
|
||||
this.outboundListeners.invoke(event, ListenerPriority.MONITOR);
|
||||
|
||||
// update the marker of the event without accidentally constructing it
|
||||
marker = NetworkMarker.getNetworkMarker(event);
|
||||
}
|
||||
|
||||
// process outbound
|
||||
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
|
||||
});
|
||||
}
|
||||
|
||||
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,34 +192,40 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
|
||||
|
||||
@Override
|
||||
public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) {
|
||||
if (!this.closed) {
|
||||
// make sure we are on the main thread if any listener of the packet needs it
|
||||
if (this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread()) {
|
||||
ProtocolLibrary.getScheduler().runTask(
|
||||
() -> this.receiveClientPacket(sender, packet, marker, filters));
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we are on the main thread if any listener of the packet needs it
|
||||
if (filters && this.requiresMainThread(packet)) {
|
||||
ProtocolLibrary.getScheduler().runTask(
|
||||
() -> this.receiveClientPacket(sender, packet, marker, filters));
|
||||
return;
|
||||
}
|
||||
|
||||
Object nmsPacket = packet.getHandle();
|
||||
|
||||
if (filters) {
|
||||
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
|
||||
this.invokeInboundPacketListeners(event);
|
||||
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object nmsPacket = packet.getHandle();
|
||||
// check to which listeners we need to post the packet
|
||||
if (filters) {
|
||||
// post to all listeners
|
||||
PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender);
|
||||
this.invokeInboundPacketListeners(event);
|
||||
if (event.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent possible de-sync
|
||||
nmsPacket = event.getPacket().getHandle();
|
||||
} else {
|
||||
// Prevent possible de-sync if the packet was replaced by a listener.
|
||||
nmsPacket = event.getPacket().getHandle();
|
||||
} else {
|
||||
// A monitor listener should never modify a packet/event so we can simply invoke monitor listeners
|
||||
// independently of our injector pipeline
|
||||
this.runMonitorListeners(packet, () -> {
|
||||
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender, false);
|
||||
this.inboundListeners.invoke(event, ListenerPriority.MONITOR);
|
||||
}
|
||||
|
||||
// post to the player inject, reset our cancel state change
|
||||
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
|
||||
});
|
||||
}
|
||||
|
||||
// post to the player inject, reset our cancel state change
|
||||
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -428,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();
|
||||
@@ -516,12 +520,25 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
|
||||
this.postPacketToListeners(this.outboundListeners, event, true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requiresMainThread(PacketContainer packet) {
|
||||
return this.hasMainThreadListener(packet.getType()) && !this.server.isPrimaryThread();
|
||||
}
|
||||
|
||||
private void runMonitorListeners(PacketContainer packet, Runnable notifyMonitor) {
|
||||
if (this.requiresMainThread(packet)) {
|
||||
ProtocolLibrary.getScheduler().runTask(notifyMonitor);
|
||||
} else {
|
||||
notifyMonitor.run();
|
||||
}
|
||||
}
|
||||
|
||||
private void postPacketToListeners(PacketListenerSet listeners, PacketEvent event, boolean outbound) {
|
||||
try {
|
||||
// append async marker if any async listener for the packet was registered
|
||||
if (this.asyncFilterManager.hasAsynchronousListeners(event)) {
|
||||
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker());
|
||||
Injector injector = this.networkManagerInjector.getInjector(event.getPlayer());
|
||||
event.setAsyncMarker(this.asyncFilterManager.createAsyncMarker(injector));
|
||||
}
|
||||
|
||||
// post to sync listeners
|
||||
|
||||
@@ -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()) {
|
||||
@@ -287,8 +325,8 @@ public class StructureCache {
|
||||
|
||||
if (registryByteBuf.isPresent()) {
|
||||
ConstructorAccessor accessor = Accessors.getConstructorAccessor(FuzzyReflection.fromClass(serializerBase, true).getConstructor(FuzzyMethodContract.newBuilder()
|
||||
.parameterDerivedOf(ByteBuf.class)
|
||||
.parameterDerivedOf(MinecraftReflection.getRegistryAccessClass())
|
||||
.parameterExactType(ByteBuf.class)
|
||||
.parameterExactType(MinecraftReflection.getRegistryAccessClass())
|
||||
.build()));
|
||||
TRICKED_DATA_SERIALIZER_BASE = (buf) -> accessor.invoke(buf, MinecraftRegistryAccess.get());
|
||||
} else {
|
||||
|
||||
+34
-25
@@ -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,38 +32,46 @@ 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 (packets.iterator().hasNext()) {
|
||||
event.getPacket().getPacketBundles().write(0, outPackets);
|
||||
} else {
|
||||
// cancel entire packet if each individual packet has been cancelled
|
||||
event.setCancelled(true);
|
||||
if (!event.isReadOnly() && 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;
|
||||
|
||||
+89
-9
@@ -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;
|
||||
@@ -29,7 +31,9 @@ import com.comphenix.protocol.injector.NetworkProcessor;
|
||||
import com.comphenix.protocol.injector.netty.Injector;
|
||||
import com.comphenix.protocol.injector.netty.WirePacket;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
@@ -99,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
|
||||
@@ -114,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;
|
||||
@@ -242,7 +251,7 @@ public class NettyChannelInjector implements Injector {
|
||||
this.uninject();
|
||||
|
||||
// remove any outgoing references
|
||||
this.channel.attr(INJECTOR).remove();
|
||||
this.channel.attr(INJECTOR).set(null);
|
||||
|
||||
// cleanup
|
||||
this.savedMarkers.clear();
|
||||
@@ -289,8 +298,7 @@ public class NettyChannelInjector implements Injector {
|
||||
|
||||
this.ensureInEventLoop(() -> {
|
||||
try {
|
||||
// try to invoke the method, this should normally not fail
|
||||
this.listenerInvoker.read(packet);
|
||||
this.channel.pipeline().context(INBOUND_INTERCEPTOR_NAME).fireChannelRead(packet);
|
||||
} catch (Exception exception) {
|
||||
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_PACKET)
|
||||
.messageParam(packet, this.playerName)
|
||||
@@ -344,14 +352,15 @@ public class NettyChannelInjector implements Injector {
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
// if the player was already resolved there is no need to do further lookups
|
||||
if (this.player != null) {
|
||||
if (this.player != null && !(this.player instanceof TemporaryPlayer)) {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
// check if the name of the player is already known to the injector
|
||||
if (this.playerName != null) {
|
||||
this.player = Bukkit.getPlayerExact(this.playerName);
|
||||
if (this.player != null) {
|
||||
Player bukkitPlayer = Bukkit.getPlayerExact(this.playerName);
|
||||
if (bukkitPlayer != null) {
|
||||
this.player = bukkitPlayer;
|
||||
this.injectionFactory.cacheInjector(this.player, this);
|
||||
}
|
||||
}
|
||||
@@ -360,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);
|
||||
@@ -420,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);
|
||||
@@ -442,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
|
||||
@@ -515,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);
|
||||
|
||||
@@ -553,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
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelConfig;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelId;
|
||||
import io.netty.channel.ChannelMetadata;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelProgressivePromise;
|
||||
@@ -33,6 +34,11 @@ final class NettyChannelProxy implements Channel {
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelId id() {
|
||||
return this.delegate.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventLoop eventLoop() {
|
||||
return this.eventLoop;
|
||||
@@ -238,6 +244,11 @@ final class NettyChannelProxy implements Channel {
|
||||
return this.delegate.attr(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean hasAttr(AttributeKey<T> attributeKey) {
|
||||
return this.delegate.hasAttr(attributeKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Channel o) {
|
||||
return this.delegate.compareTo(o);
|
||||
|
||||
+18
-1
@@ -10,11 +10,13 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.netty.channel.AbstractEventLoop;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.util.concurrent.AbstractEventExecutor;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.ProgressivePromise;
|
||||
@@ -25,7 +27,7 @@ import io.netty.util.concurrent.ScheduledFuture;
|
||||
* An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which
|
||||
* schedule something on the event loop to methods which decide what should happen to the scheduled task.
|
||||
*/
|
||||
final class NettyEventLoopProxy implements EventLoop {
|
||||
final class NettyEventLoopProxy extends AbstractEventLoop {
|
||||
|
||||
private static final Callable<?> EMPTY_CALLABLE = () -> null;
|
||||
private static final Runnable EMPTY_RUNNABLE = () -> {
|
||||
@@ -236,6 +238,15 @@ final class NettyEventLoopProxy implements EventLoop {
|
||||
}
|
||||
}
|
||||
|
||||
// ShreddedPaper uses this method
|
||||
@Override
|
||||
public void lazyExecute(Runnable command) {
|
||||
Runnable proxied = this.proxyRunnable(command);
|
||||
if (proxied != null) {
|
||||
((AbstractEventExecutor)this.delegate).lazyExecute(proxied);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super EventExecutor> action) {
|
||||
this.delegate.forEach(action);
|
||||
@@ -251,6 +262,12 @@ final class NettyEventLoopProxy implements EventLoop {
|
||||
return this.delegate.register(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelFuture register(ChannelPromise channelPromise) {
|
||||
return this.delegate.register(channelPromise);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public ChannelFuture register(Channel channel, ChannelPromise promise) {
|
||||
return this.delegate.register(channel, promise);
|
||||
|
||||
+4
-25
@@ -14,13 +14,11 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
/**
|
||||
* This class facilitates the invocation of methods on the current packet listener.
|
||||
* It attempts to execute the <code>send</code>, <code>read</code>, and <code>disconnect</code>
|
||||
* methods and, upon failure (either due to the absence of the method or the packet
|
||||
* listener being of an incorrect type), it delegates the call to the network manager.
|
||||
* It attempts to execute the <code>send</code>, and <code>disconnect</code> methods, upon failure
|
||||
* (either due to the absence of the method or the packet listener being of an incorrect type),
|
||||
* it delegates the call to the network manager.
|
||||
*
|
||||
* <p>Supported packet listener types include CONFIGURATION and PLAY. If the packet
|
||||
* listener does not match these types, or if the required method is missing, the
|
||||
@@ -43,7 +41,6 @@ public class PacketListenerInvoker {
|
||||
private static final boolean DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT = doesPacketListenerDisconnectUseComponent();
|
||||
|
||||
private static final MethodAccessor NETWORK_MANAGER_SEND = getNetworkManagerSend();
|
||||
private static final MethodAccessor NETWORK_MANAGER_READ = getNetworkManagerRead();
|
||||
private static final MethodAccessor NETWORK_MANAGER_DISCONNECT = getNetworkManagerDisconnect();
|
||||
private static final MethodAccessor NETWORK_MANAGER_PACKET_LISTENER = getNetworkManagerPacketListener();
|
||||
|
||||
@@ -117,15 +114,6 @@ public class PacketListenerInvoker {
|
||||
return Accessors.getMethodAccessor(send);
|
||||
}
|
||||
|
||||
private static MethodAccessor getNetworkManagerRead() {
|
||||
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true);
|
||||
|
||||
Method read = networkManager
|
||||
.getMethodByParameters("read", ChannelHandlerContext.class, MinecraftReflection.getPacketClass());
|
||||
|
||||
return Accessors.getMethodAccessor(read);
|
||||
}
|
||||
|
||||
private static MethodAccessor getNetworkManagerDisconnect() {
|
||||
FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass());
|
||||
|
||||
@@ -190,7 +178,7 @@ public class PacketListenerInvoker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet using the current packet listener if available and valid; otherwise,
|
||||
* Sends a packet using the current packet listener if available and valid; otherwise,
|
||||
* falls back to the network manager.
|
||||
*
|
||||
* @param packet The packet to be sent.
|
||||
@@ -204,15 +192,6 @@ public class PacketListenerInvoker {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a packet directly using the network manager.
|
||||
*
|
||||
* @param packet The packet to be read.
|
||||
*/
|
||||
public void read(Object packet) {
|
||||
NETWORK_MANAGER_READ.invoke(this.networkManager, null, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the player using the current packet listener if available and valid; otherwise,
|
||||
* falls back to the network manager.
|
||||
|
||||
+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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,6 @@ package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -31,18 +29,15 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.injector.packet.internal.ProtocolRegistry_1_20_5;
|
||||
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.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
@@ -70,7 +65,7 @@ public class PacketRegistry {
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class Register {
|
||||
public static class Register {
|
||||
// The main lookup table
|
||||
final Map<PacketType, Optional<Class<?>>> typeToClass = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -82,14 +77,18 @@ public class PacketRegistry {
|
||||
volatile Set<PacketType> clientPackets = new HashSet<>();
|
||||
final List<MapContainer> containers = new ArrayList<>();
|
||||
|
||||
public Register() {
|
||||
private Register() {
|
||||
}
|
||||
|
||||
public void registerPacket(PacketType type, Class<?> clazz, Sender sender) {
|
||||
typeToClass.put(type, Optional.of(clazz));
|
||||
public void registerPacket(PacketType type, Class<?> packetClass, Sender sender, WrappedStreamCodec codec) {
|
||||
typeToClass.put(type, Optional.of(packetClass));
|
||||
classToType.put(packetClass, type);
|
||||
|
||||
classToType.put(clazz, type);
|
||||
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(clazz, type);
|
||||
if (codec != null) {
|
||||
classToCodec.put(packetClass, codec);
|
||||
}
|
||||
|
||||
protocolClassToType.computeIfAbsent(type.getProtocol(), __ -> new ConcurrentHashMap<>()).put(packetClass, type);
|
||||
|
||||
if (sender == Sender.CLIENT) {
|
||||
clientPackets.add(type);
|
||||
@@ -293,206 +292,9 @@ public class PacketRegistry {
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static synchronized Register createRegisterV1_20_5() {
|
||||
Object[] protocols = ENUM_PROTOCOL.getEnumConstants();
|
||||
|
||||
// PacketType<?> to class map
|
||||
final Map<Object, Class<?>> packetTypeMap = new HashMap<>();
|
||||
|
||||
// List of all class containing PacketTypes
|
||||
String[] packetTypesClassNames = new String[] {
|
||||
"common.CommonPacketTypes",
|
||||
"configuration.ConfigurationPacketTypes",
|
||||
"cookie.CookiePacketTypes",
|
||||
"game.GamePacketTypes",
|
||||
"handshake.HandshakePacketTypes",
|
||||
"login.LoginPacketTypes",
|
||||
"ping.PingPacketTypes",
|
||||
"status.StatusPacketTypes"
|
||||
};
|
||||
|
||||
Class<?> packetTypeClass = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");
|
||||
|
||||
for (String packetTypesClassName : packetTypesClassNames) {
|
||||
Class<?> packetTypesClass = MinecraftReflection
|
||||
.getOptionalNMS("network.protocol." + packetTypesClassName)
|
||||
.orElse(null);
|
||||
if (packetTypesClass == null) {
|
||||
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check every field for "static final PacketType<?>"
|
||||
for (Field field : packetTypesClass.getDeclaredFields()) {
|
||||
try {
|
||||
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object packetType = field.get(null);
|
||||
if (!packetTypeClass.isInstance(packetType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// retrieve the generic type T of the PacketType<T> field
|
||||
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
|
||||
if (packet instanceof Class<?>) {
|
||||
packetTypeMap.put(packetType, (Class<?>) packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ID to Packet class maps
|
||||
final Map<Object, Map<Class<?>, Integer>> serverMaps = new LinkedHashMap<>();
|
||||
final Map<Object, Map<Class<?>, Integer>> clientMaps = new LinkedHashMap<>();
|
||||
|
||||
// global registry instance
|
||||
final Register result = new Register();
|
||||
|
||||
// List of all class containing ProtocolInfos
|
||||
String[] protocolClassNames = new String[] {
|
||||
"configuration.ConfigurationProtocols",
|
||||
"game.GameProtocols",
|
||||
"handshake.HandshakeProtocols",
|
||||
"login.LoginProtocols",
|
||||
"status.StatusProtocols"
|
||||
};
|
||||
|
||||
Class<?> protocolInfoClass = MinecraftReflection.getProtocolInfoClass();
|
||||
Class<?> protocolInfoUnboundClass = MinecraftReflection.getProtocolInfoUnboundClass();
|
||||
Class<?> streamCodecClass = MinecraftReflection.getStreamCodecClass();
|
||||
Class<?> idCodecClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec");
|
||||
Class<?> idCodecEntryClass = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec$Entry", "network.codec.IdDispatchCodec$b");
|
||||
Class<?> protocolDirectionClass = MinecraftReflection.getPacketFlowClass();
|
||||
|
||||
Function<?, ?> emptyFunction = input -> input;
|
||||
|
||||
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(protocolInfoClass);
|
||||
|
||||
MethodAccessor protocolAccessor = Accessors.getMethodAccessor(protocolInfoReflection
|
||||
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
|
||||
|
||||
MethodAccessor directionAccessor = Accessors.getMethodAccessor(protocolInfoReflection
|
||||
.getMethodByReturnTypeAndParameters("flow", protocolDirectionClass, new Class[0]));
|
||||
|
||||
MethodAccessor codecAccessor = Accessors.getMethodAccessor(
|
||||
protocolInfoReflection.getMethodByReturnTypeAndParameters("codec", streamCodecClass, new Class[0]));
|
||||
|
||||
MethodAccessor bindAccessor = Accessors.getMethodAccessor(FuzzyReflection.fromClass(protocolInfoUnboundClass)
|
||||
.getMethodByReturnTypeAndParameters("bind", protocolInfoClass, new Class[] { Function.class }));
|
||||
|
||||
FuzzyReflection idCodecReflection = FuzzyReflection.fromClass(idCodecClass, true);
|
||||
|
||||
FieldAccessor byIdAccessor = Accessors.getFieldAccessor(idCodecReflection
|
||||
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(List.class).build()));
|
||||
|
||||
FieldAccessor toIdAccessor = Accessors.getFieldAccessor(idCodecReflection
|
||||
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).build()));
|
||||
|
||||
FuzzyReflection idCodecEntryReflection = FuzzyReflection.fromClass(idCodecEntryClass, true);
|
||||
|
||||
MethodAccessor idCodecEntryTypeAccessor = Accessors.getMethodAccessor(idCodecEntryReflection
|
||||
.getMethodByReturnTypeAndParameters("type", Object.class, new Class[0]));
|
||||
|
||||
MethodAccessor idCodecEntrySerializerAccessor = Accessors.getMethodAccessor(idCodecEntryReflection
|
||||
.getMethodByReturnTypeAndParameters("serializer", streamCodecClass, new Class[0]));
|
||||
|
||||
for (String protocolClassName : protocolClassNames) {
|
||||
Class<?> protocolClass = MinecraftReflection
|
||||
.getOptionalNMS("network.protocol." + protocolClassName)
|
||||
.orElse(null);
|
||||
if (protocolClass == null) {
|
||||
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocolClassName);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Field field : protocolClass.getDeclaredFields()) {
|
||||
try {
|
||||
// ignore none static and final fields
|
||||
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object protocolInfo = field.get(null);
|
||||
|
||||
// bind unbound ProtocolInfo to empty function to get real ProtocolInfo
|
||||
if (protocolInfoUnboundClass.isInstance(protocolInfo)) {
|
||||
protocolInfo = bindAccessor.invoke(protocolInfo, new Object[] { emptyFunction });
|
||||
}
|
||||
|
||||
// ignore any field that isn't a ProtocolInfo
|
||||
if (!protocolInfoClass.isInstance(protocolInfo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get codec and check if codec is instance of IdDispatchCodec
|
||||
// since that is the only support codec as of now
|
||||
Object codec = codecAccessor.invoke(protocolInfo);
|
||||
if (!idCodecClass.isInstance(codec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// retrieve packetTypeMap and convert it to packetIdMap
|
||||
Map<Class<?>, Integer> packetMap = new HashMap<>();
|
||||
List<Object> serializerList = (List<Object>) byIdAccessor.get(codec);
|
||||
Map<Object, Integer> packetTypeIdMap = (Map<Object, Integer>) toIdAccessor.get(codec);
|
||||
|
||||
for (Map.Entry<Object, Integer> entry : packetTypeIdMap.entrySet()) {
|
||||
Class<?> packetClass = packetTypeMap.get(entry.getKey());
|
||||
if (packetClass == null) {
|
||||
throw new RuntimeException("packetType missing packet " + entry.getKey());
|
||||
}
|
||||
|
||||
packetMap.put(packetClass, entry.getValue());
|
||||
}
|
||||
|
||||
// retrieve packet codecs for packet construction and write methods
|
||||
for (Object entry : serializerList) {
|
||||
Object packetType = idCodecEntryTypeAccessor.invoke(entry);
|
||||
|
||||
Class<?> packetClass = packetTypeMap.get(packetType);
|
||||
if (packetClass == null) {
|
||||
throw new RuntimeException("packetType missing packet " + packetType);
|
||||
}
|
||||
|
||||
Object serializer = idCodecEntrySerializerAccessor.invoke(entry);
|
||||
result.classToCodec.put(packetClass, new WrappedStreamCodec(serializer));
|
||||
}
|
||||
|
||||
// get EnumProtocol and Direction of protocol info
|
||||
Object protocol = protocolAccessor.invoke(protocolInfo);
|
||||
String direction = directionAccessor.invoke(protocolInfo).toString();
|
||||
|
||||
if (direction.contains("CLIENTBOUND")) { // Sent by Server
|
||||
serverMaps.put(protocol, packetMap);
|
||||
} else if (direction.contains("SERVERBOUND")) { // Sent by Client
|
||||
clientMaps.put(protocol, packetMap);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Object protocol : protocols) {
|
||||
Enum<?> enumProtocol = (Enum<?>) protocol;
|
||||
PacketType.Protocol equivalent = PacketType.Protocol.fromVanilla(enumProtocol);
|
||||
|
||||
// Associate known types
|
||||
if (serverMaps.containsKey(protocol)) {
|
||||
associatePackets(result, reverse(serverMaps.get(protocol)), equivalent, Sender.SERVER);
|
||||
}
|
||||
if (clientMaps.containsKey(protocol)) {
|
||||
associatePackets(result, reverse(clientMaps.get(protocol)), equivalent, Sender.CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolRegistry_1_20_5.fillRegister(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -517,7 +319,7 @@ public class PacketRegistry {
|
||||
PacketType type = PacketType.fromCurrent(protocol, sender, packetId, packetClass);
|
||||
|
||||
try {
|
||||
register.registerPacket(type, packetClass, sender);
|
||||
register.registerPacket(type, packetClass, sender, null);
|
||||
} catch (Exception ex) {
|
||||
ProtocolLogger.debug("Encountered an exception associating packet " + type, ex);
|
||||
}
|
||||
@@ -610,8 +412,8 @@ public class PacketRegistry {
|
||||
private static Class<?> searchForPacket(List<String> classNames) {
|
||||
for (String name : classNames) {
|
||||
try {
|
||||
Class<?> clazz = MinecraftReflection.getMinecraftClass(name);
|
||||
if (MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
|
||||
Class<?> clazz = resolvePacketClassName(name);
|
||||
if (clazz != null && MinecraftReflection.getPacketClass().isAssignableFrom(clazz)
|
||||
&& !Modifier.isAbstract(clazz.getModifiers())) {
|
||||
return clazz;
|
||||
}
|
||||
@@ -622,6 +424,22 @@ public class PacketRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a single packet class name. Fully-qualified names (those already containing their
|
||||
* package, e.g. {@code net.minecraft.network.protocol.game.XPacket}) are loaded directly, since
|
||||
* {@link MinecraftReflection#getMinecraftClass(String)} would otherwise prepend the Minecraft
|
||||
* package and fail to find them. Relative names continue to be resolved against the Minecraft
|
||||
* package.
|
||||
*/
|
||||
private static Class<?> resolvePacketClassName(String name) {
|
||||
Optional<Class<?>> direct = MinecraftReflection.getOptionalClass(name);
|
||||
if (direct.isPresent()) {
|
||||
return direct.get();
|
||||
}
|
||||
|
||||
return MinecraftReflection.getMinecraftClass(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the correct packet class from a given type.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.comphenix.protocol.injector.packet.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.AbstractWrapper;
|
||||
import com.comphenix.protocol.wrappers.WrappedStreamCodec;
|
||||
|
||||
/**
|
||||
* Class only works for version 1.20.5+
|
||||
*/
|
||||
public class IdCodecWrapper extends AbstractWrapper {
|
||||
|
||||
private static final Class<?> ID_CODEC_CLASS;
|
||||
private static final FieldAccessor BY_ID_ACCESSOR;
|
||||
|
||||
private static final Class<?> ID_CODEC_ENTRY_CLASS;
|
||||
private static final MethodAccessor ENTRY_TYPE_ACCESSOR;
|
||||
private static final MethodAccessor ENTRY_SERIALIZER_ACCESSOR;
|
||||
|
||||
static {
|
||||
ID_CODEC_CLASS = MinecraftReflection.getMinecraftClass("network.codec.IdDispatchCodec");
|
||||
BY_ID_ACCESSOR = Accessors.getFieldAccessor(FuzzyReflection.fromClass(ID_CODEC_CLASS, true)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeDerivedOf(List.class).build()));
|
||||
|
||||
ID_CODEC_ENTRY_CLASS = Arrays.stream(ID_CODEC_CLASS.getNestMembers())
|
||||
.filter(Class::isRecord)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("Failed to find entry class for: " + ID_CODEC_CLASS));
|
||||
|
||||
FuzzyReflection entryReflection = FuzzyReflection.fromClass(ID_CODEC_ENTRY_CLASS, true);
|
||||
ENTRY_TYPE_ACCESSOR = Accessors.getMethodAccessor(entryReflection
|
||||
.getMethodByReturnTypeAndParameters("type", Object.class, new Class[0]));
|
||||
ENTRY_SERIALIZER_ACCESSOR = Accessors.getMethodAccessor(entryReflection
|
||||
.getMethodByReturnTypeAndParameters("serializer", MinecraftReflection.getStreamCodecClass(), new Class[0]));
|
||||
}
|
||||
|
||||
public IdCodecWrapper(Object handle) {
|
||||
super(ID_CODEC_CLASS);
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
public List<Entry> getById() {
|
||||
// list represents a packet-id to {type, codec} lookup
|
||||
List<?> byId = (List<?>) BY_ID_ACCESSOR.get(this.handle);
|
||||
return byId.stream().map(Entry::new).toList();
|
||||
}
|
||||
|
||||
public class Entry extends AbstractWrapper {
|
||||
|
||||
public Entry(Object handle) {
|
||||
super(ID_CODEC_ENTRY_CLASS);
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
public Object type() {
|
||||
return ENTRY_TYPE_ACCESSOR.invoke(this.handle);
|
||||
}
|
||||
|
||||
public WrappedStreamCodec serializer() {
|
||||
Object serializer = ENTRY_SERIALIZER_ACCESSOR.invoke(this.handle);
|
||||
return new WrappedStreamCodec(serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.comphenix.protocol.injector.packet.internal;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Class only works for version 1.21.5+
|
||||
*/
|
||||
public class ProtocolContexts {
|
||||
|
||||
private static final Class<?> GAME_PROTOCOL_CONTEXT_CLASS;
|
||||
|
||||
static {
|
||||
GAME_PROTOCOL_CONTEXT_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"network.protocol.game.GameProtocols$a" /*Spigot Mapping*/,
|
||||
"network.protocol.game.GameProtocols$Context" /*Mojang Mapping*/);
|
||||
}
|
||||
|
||||
public static Object createGameProtocolContext() {
|
||||
return createContext(GAME_PROTOCOL_CONTEXT_CLASS, (proxy, method, args) -> {
|
||||
if (method.getName().equals("hasInfiniteMaterials") || (method.getReturnType() == boolean.class && method.getParameterCount() == 0)) {
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static Object createContext(Class<?> interfaceClass, InvocationHandler handler) {
|
||||
if (interfaceClass == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Proxy.newProxyInstance(ProtocolContexts.class.getClassLoader(), new Class[] { interfaceClass }, new InvocationHandler() {
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
return handler.invoke(proxy, method, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.comphenix.protocol.injector.packet.internal;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.AbstractWrapper;
|
||||
|
||||
/**
|
||||
* Class only works for version 1.20.5+
|
||||
*/
|
||||
public class ProtocolInfoWrapper extends AbstractWrapper {
|
||||
|
||||
private static final @NotNull Class<?> SIMPLE_UNBOUND_PROTOCOL_CLASS;
|
||||
private static final @NotNull MethodAccessor BIND_SIMPLE_ACCESSOR;
|
||||
|
||||
private static final @Nullable Class<?> UNBOUND_PROTOCOL_CLASS;
|
||||
private static final @Nullable MethodAccessor BIND_CONTEXT_ACCESSOR;
|
||||
|
||||
private static final @NotNull MethodAccessor ID_ACCESSOR;
|
||||
private static final @NotNull MethodAccessor FLOW_ACCESSOR;
|
||||
private static final @NotNull MethodAccessor CODEC_ACCESSOR;
|
||||
|
||||
static {
|
||||
SIMPLE_UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getMinecraftClass(
|
||||
"network.protocol.SimpleUnboundProtocol" /* 1.21.5+ */,
|
||||
"network.protocol.UnboundProtocol" /* 1.21.8+ Alternative */,
|
||||
"network.ProtocolInfo$a" /* Spigot Mappings */,
|
||||
"network.ProtocolInfo$Unbound" /* Mojang Mappings */);
|
||||
BIND_SIMPLE_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(SIMPLE_UNBOUND_PROTOCOL_CLASS)
|
||||
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class }));
|
||||
|
||||
UNBOUND_PROTOCOL_CLASS = MinecraftReflection.getNullableNMS("network.protocol.UnboundProtocol");
|
||||
if (UNBOUND_PROTOCOL_CLASS != null) {
|
||||
BIND_CONTEXT_ACCESSOR = Accessors.getMethodAccessor(FuzzyReflection.fromClass(UNBOUND_PROTOCOL_CLASS)
|
||||
.getMethodByReturnTypeAndParameters("bind", MinecraftReflection.getProtocolInfoClass(), new Class[] { Function.class, Object.class }));
|
||||
} else {
|
||||
BIND_CONTEXT_ACCESSOR = null;
|
||||
}
|
||||
|
||||
FuzzyReflection protocolInfoReflection = FuzzyReflection.fromClass(MinecraftReflection.getProtocolInfoClass());
|
||||
ID_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
|
||||
.getMethodByReturnTypeAndParameters("id", MinecraftReflection.getEnumProtocolClass(), new Class[0]));
|
||||
FLOW_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
|
||||
.getMethodByReturnTypeAndParameters("flow", MinecraftReflection.getPacketFlowClass(), new Class[0]));
|
||||
CODEC_ACCESSOR = Accessors.getMethodAccessor(protocolInfoReflection
|
||||
.getMethodByReturnTypeAndParameters("codec", MinecraftReflection.getStreamCodecClass(), new Class[0]));
|
||||
}
|
||||
|
||||
public static boolean isUnboundProtocol(Object protocol) {
|
||||
return SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol) || (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol));
|
||||
}
|
||||
|
||||
public static ProtocolInfoWrapper fromUnbound(Object protocol, Object context) {
|
||||
// bind to identity function in-order to map directly to FriendlyByteBuf instead of ByteBuf
|
||||
|
||||
if (SIMPLE_UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
|
||||
Object protocolInfo = BIND_SIMPLE_ACCESSOR.invoke(protocol, Function.identity());
|
||||
return new ProtocolInfoWrapper(protocolInfo);
|
||||
}
|
||||
|
||||
if (UNBOUND_PROTOCOL_CLASS != null && UNBOUND_PROTOCOL_CLASS.isInstance(protocol)) {
|
||||
Object protocolInfo = BIND_CONTEXT_ACCESSOR.invoke(protocol, Function.identity(), Objects.requireNonNull(context));
|
||||
return new ProtocolInfoWrapper(protocolInfo);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown protocol implementation: " + protocol);
|
||||
}
|
||||
|
||||
private ProtocolInfoWrapper(Object handle) {
|
||||
super(MinecraftReflection.getProtocolInfoClass());
|
||||
setHandle(handle);
|
||||
}
|
||||
|
||||
public PacketType.Protocol id() {
|
||||
Object protocol = ID_ACCESSOR.invoke(this.handle);
|
||||
return PacketType.Protocol.fromVanilla((Enum<?>) protocol);
|
||||
}
|
||||
|
||||
public PacketType.Sender flow() {
|
||||
String packetFlow = FLOW_ACCESSOR.invoke(this.handle).toString();
|
||||
|
||||
if (packetFlow.contains("CLIENTBOUND")) {
|
||||
return PacketType.Sender.SERVER;
|
||||
} else if (packetFlow.contains("SERVERBOUND")) {
|
||||
return PacketType.Sender.CLIENT;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Unknown packet flow: " + packetFlow);
|
||||
}
|
||||
|
||||
public IdCodecWrapper codec() {
|
||||
return new IdCodecWrapper(CODEC_ACCESSOR.invoke(this.handle));
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package com.comphenix.protocol.injector.packet.internal;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLogger;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry.Register;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
/**
|
||||
* Class only works for version 1.20.5+
|
||||
*/
|
||||
public class ProtocolRegistry_1_20_5 {
|
||||
|
||||
private static final Class<?> PACKET_TYPE_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.PacketType");;
|
||||
|
||||
/**
|
||||
* List of all class containing PacketTypes
|
||||
*/
|
||||
public static final List<String> PACKET_TYPES_CLASS_NAMES = List.of(
|
||||
"common.CommonPacketTypes",
|
||||
"configuration.ConfigurationPacketTypes",
|
||||
"cookie.CookiePacketTypes",
|
||||
"game.GamePacketTypes",
|
||||
"handshake.HandshakePacketTypes",
|
||||
"login.LoginPacketTypes",
|
||||
"ping.PingPacketTypes",
|
||||
"status.StatusPacketTypes");
|
||||
|
||||
/**
|
||||
* List of all class containing ProtocolInfos
|
||||
*/
|
||||
public static final List<Protocol> PROTOCOLS = List.of(
|
||||
new Protocol("configuration.ConfigurationProtocols", null),
|
||||
new Protocol("game.GameProtocols", ProtocolContexts::createGameProtocolContext),
|
||||
new Protocol("handshake.HandshakeProtocols", null),
|
||||
new Protocol("login.LoginProtocols", null),
|
||||
new Protocol("status.StatusProtocols", null));
|
||||
|
||||
public static void fillRegister(Register register) {
|
||||
final Map<Object, Class<?>> packetTypeMap = getPacketTypes();
|
||||
|
||||
for (Protocol protocol : PROTOCOLS) {
|
||||
Class<?> protocolClass = MinecraftReflection.getNullableNMS("network.protocol." + protocol.className());
|
||||
if (protocolClass == null) {
|
||||
ProtocolLogger.debug("Can't find protocol class: {0}, will skip it", protocol);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Field field : protocolClass.getDeclaredFields()) {
|
||||
try {
|
||||
// ignore none static and final fields
|
||||
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object fieldValue = field.get(null);
|
||||
|
||||
// Skip any field that isn't a unbound ProtocolInfo.
|
||||
// We need to bind the ProtocolInfo ourself as we apply our own stream mapping
|
||||
// inside the ProtocolInfoWrapper::fromUnbound method
|
||||
if (!ProtocolInfoWrapper.isUnboundProtocol(fieldValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// create optional context object to bind protocol to
|
||||
Object context = protocol.context() != null
|
||||
? protocol.context().get()
|
||||
: null;
|
||||
|
||||
// wrap bound protocol info for ease of use
|
||||
ProtocolInfoWrapper protocolInfo = ProtocolInfoWrapper.fromUnbound(fieldValue, context);
|
||||
PacketType.Protocol packetProtocol = protocolInfo.id();
|
||||
PacketType.Sender packetSender = protocolInfo.flow();
|
||||
|
||||
// register each packet to Register instance
|
||||
List<IdCodecWrapper.Entry> entries = protocolInfo.codec().getById();
|
||||
for (int id = 0; id < entries.size(); id++) {
|
||||
IdCodecWrapper.Entry entry = entries.get(id);
|
||||
|
||||
Class<?> packetClass = packetTypeMap.get(entry.type());
|
||||
if (packetClass == null) {
|
||||
throw new RuntimeException("packetType missing packet " + entry.type());
|
||||
}
|
||||
|
||||
PacketType packetType = PacketType.fromCurrent(packetProtocol, packetSender, id, packetClass);
|
||||
// TODO(fix): validate that every codec expects FriendlyByteBuf
|
||||
register.registerPacket(packetType, packetClass, packetSender, entry.serializer());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a PacketType to packet class lookup map
|
||||
*/
|
||||
private static Map<Object, Class<?>> getPacketTypes() {
|
||||
Map<Object, Class<?>> packetTypeMap = new HashMap<>();
|
||||
|
||||
for (String packetTypesClassName : PACKET_TYPES_CLASS_NAMES) {
|
||||
Class<?> packetTypesClass = MinecraftReflection.getNullableNMS("network.protocol." + packetTypesClassName);
|
||||
if (packetTypesClass == null) {
|
||||
ProtocolLogger.debug("Can't find PacketType class: {0}, will skip it", packetTypesClassName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// check every field for "static final PacketType<?>"
|
||||
for (Field field : packetTypesClass.getDeclaredFields()) {
|
||||
try {
|
||||
if (!Modifier.isFinal(field.getModifiers()) || !Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object packetType = field.get(null);
|
||||
if (!PACKET_TYPE_CLASS.isInstance(packetType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// retrieve the generic type T of the PacketType<T> field
|
||||
Type packet = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
|
||||
if (packet instanceof Class<?>) {
|
||||
packetTypeMap.put(packetType, (Class<?>) packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packetTypeMap;
|
||||
}
|
||||
|
||||
private record Protocol(@NotNull String className, @Nullable Supplier<Object> context) {
|
||||
}
|
||||
}
|
||||
+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/";
|
||||
|
||||
@@ -88,20 +88,23 @@ final class CachedPackage {
|
||||
* @throws RuntimeException If we are unable to find the given class.
|
||||
*/
|
||||
public Optional<Class<?>> getPackageClass(String className, String... aliases) {
|
||||
return cache.computeIfAbsent(className, x -> {
|
||||
Optional<Class<?>> clazz = resolveClass(className);
|
||||
if (clazz.isPresent()) {
|
||||
return clazz;
|
||||
}
|
||||
Optional<Class<?>> cached = cache.get(className);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
Optional<Class<?>> clazz = resolveClass(className);
|
||||
if (!clazz.isPresent()) {
|
||||
for (String alias : aliases) {
|
||||
clazz = resolveClass(alias);
|
||||
if (clazz.isPresent()) {
|
||||
return clazz;
|
||||
Optional<Class<?>> aliasClazz = resolveClass(alias);
|
||||
if (aliasClazz.isPresent()) {
|
||||
clazz = aliasClazz;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
cache.put(className, clazz);
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,6 +93,15 @@ public final class MinecraftProtocolVersion {
|
||||
map.put(new MinecraftVersion(1, 20, 5), 766);
|
||||
|
||||
map.put(new MinecraftVersion(1, 21, 0), 767);
|
||||
map.put(new MinecraftVersion(1, 21, 2), 768);
|
||||
map.put(new MinecraftVersion(1, 21, 6), 771);
|
||||
map.put(new MinecraftVersion(1, 21, 8), 772);
|
||||
map.put(new MinecraftVersion(1, 21, 9), 773);
|
||||
map.put(new MinecraftVersion(1, 21, 11), 774);
|
||||
|
||||
map.put(new MinecraftVersion(26, 1, 0), 775);
|
||||
map.put(new MinecraftVersion(26, 2, 0), 776);
|
||||
map.put(new MinecraftVersion(26, 3, 0), 777);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
@@ -553,6 +553,10 @@ public final class MinecraftReflection {
|
||||
return getClass("com.mojang.authlib.GameProfile");
|
||||
}
|
||||
|
||||
public static Class<?> getGameProfilePropertyMapClass() {
|
||||
return getClass("com.mojang.authlib.properties.PropertyMap");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entity (NMS) class.
|
||||
*
|
||||
@@ -685,7 +689,8 @@ public final class MinecraftReflection {
|
||||
* @throws IllegalStateException If the class could not be found or deduced.
|
||||
*/
|
||||
public static Class<?> getChatSerializerClass() {
|
||||
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer");
|
||||
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer",
|
||||
"network.chat.Component$Serializer", "network.chat.ComponentSerialization", "IChatBaseComponent$ChatSerializer");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -916,15 +921,19 @@ public final class MinecraftReflection {
|
||||
}
|
||||
|
||||
public static Class<?> getMinecraftKeyClass() {
|
||||
return getMinecraftClass("resources.MinecraftKey", "resources.ResourceLocation", "MinecraftKey");
|
||||
return getMinecraftClass("resources.MinecraftKey", "resources.Identifier", "resources.ResourceLocation", "MinecraftKey");
|
||||
}
|
||||
|
||||
public static Class<?> getMobEffectListClass() {
|
||||
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect");
|
||||
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect", "world.effect.MobEffects");
|
||||
}
|
||||
|
||||
public static Class<?> getDamageTypeClass() {
|
||||
return getMinecraftClass("world.damagesource.DamageType");
|
||||
}
|
||||
|
||||
public static Class<?> getSoundEffectClass() {
|
||||
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect");
|
||||
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect", "sounds.SoundEvents");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1113,6 +1122,15 @@ public final class MinecraftReflection {
|
||||
return getOptionalNMS("network.chat.numbers.StyledFormat");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the NMS TeamColor class, added in 26.2.
|
||||
*
|
||||
* @return The TeamColor class.
|
||||
*/
|
||||
public static Optional<Class<?>> getTeamColorClass() {
|
||||
return getOptionalNMS("world.scores.TeamColor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Gson class used by Minecraft.
|
||||
*
|
||||
@@ -1412,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1475,6 +1498,18 @@ public final class MinecraftReflection {
|
||||
.orElseThrow(() -> new RuntimeException("Cannot find class " + className));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load a class by its fully-qualified canonical name, without prepending the
|
||||
* Minecraft package. Unlike {@link #getMinecraftClass(String)}, this can resolve names that
|
||||
* already include their package (e.g. {@code net.minecraft.network.protocol.game.XPacket}).
|
||||
*
|
||||
* @param className - the fully-qualified class name.
|
||||
* @return Optional that may contain the class.
|
||||
*/
|
||||
public static Optional<Class<?>> getOptionalClass(String className) {
|
||||
return getClassSource().loadClass(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class object of a specific CraftBukkit class.
|
||||
*
|
||||
@@ -1679,7 +1714,7 @@ public final class MinecraftReflection {
|
||||
}
|
||||
|
||||
public static Class<?> getEntityTypes() {
|
||||
return getMinecraftClass("world.entity.EntityTypes", "world.entity.EntityType", "EntityTypes");
|
||||
return getMinecraftClass("world.entity.EntityType", "world.entity.EntityTypes", "EntityTypes");
|
||||
}
|
||||
|
||||
public static Class<?> getParticleParam() {
|
||||
@@ -1898,4 +1933,31 @@ public final class MinecraftReflection {
|
||||
|
||||
return isMojangMapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the PositionMoveRotation class (introduced in 1.21.2).
|
||||
*
|
||||
* @return The PositionMoveRotation class.
|
||||
*/
|
||||
public static Class<?> getPositionMoveRotationClass() {
|
||||
return getMinecraftClass("world.entity.PositionMoveRotation");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.comphenix.protocol.utility;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
@@ -17,6 +18,7 @@ public class MinecraftRegistryAccess {
|
||||
|
||||
private static MethodAccessor GET_SERVER = null;
|
||||
private static MethodAccessor REGISTRY_ACCESS = null;
|
||||
private static MethodAccessor CREATE_SERIALIZATION_CONTEXT = null;
|
||||
|
||||
// lazy initialized
|
||||
private static Object registryAccess = null;
|
||||
@@ -36,6 +38,14 @@ public class MinecraftRegistryAccess {
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnDerivedOf(MinecraftReflection.getRegistryAccessClass())
|
||||
.build()));
|
||||
|
||||
CREATE_SERIALIZATION_CONTEXT = Accessors.getMethodAccessor(
|
||||
FuzzyReflection.fromClass(MinecraftReflection.getHolderLookupProviderClass(), false)
|
||||
.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnDerivedOf(MinecraftReflection.getDynamicOpsClass())
|
||||
.parameterDerivedOf(MinecraftReflection.getDynamicOpsClass())
|
||||
.build()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +67,13 @@ public class MinecraftRegistryAccess {
|
||||
|
||||
return registryAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a RegistryOps using the given DynamicOps as the underlying ops.
|
||||
*
|
||||
* @return a RegistryOps
|
||||
*/
|
||||
public static WrappedDynamicOps createSerializationContext(WrappedDynamicOps dynamicOps) {
|
||||
return WrappedDynamicOps.fromHandle(CREATE_SERIALIZATION_CONTEXT.invoke(get(), dynamicOps.getHandle()));
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,56 @@ 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
|
||||
*/
|
||||
public static final MinecraftVersion v26_2 = new MinecraftVersion("26.2");
|
||||
|
||||
/**
|
||||
* Version 26.1 - tiny takeover
|
||||
*/
|
||||
public static final MinecraftVersion v26_1 = new MinecraftVersion("26.1");
|
||||
|
||||
/**
|
||||
* Version 1.21.11 - mounts of mayhem
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_11 = new MinecraftVersion("1.21.11");
|
||||
|
||||
/**
|
||||
* Version 1.21.10 - hotfix for 1.21.9
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_10 = new MinecraftVersion("1.21.10");
|
||||
|
||||
/**
|
||||
* Version 1.21.9 - the copper age drop
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_9 = new MinecraftVersion("1.21.9");
|
||||
|
||||
/**
|
||||
* Version 1.21.6 - chase the skies
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_6 = new MinecraftVersion("1.21.6");
|
||||
|
||||
/**
|
||||
* Version 1.21.5 - spring to life drop
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_5 = new MinecraftVersion("1.21.5");
|
||||
|
||||
/**
|
||||
* Version 1.21.4 - the garden awakens drop
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_4 = new MinecraftVersion("1.21.4");
|
||||
|
||||
/**
|
||||
* Version 1.21.2 - the bundles of bravery drop
|
||||
*/
|
||||
public static final MinecraftVersion v1_21_2 = new MinecraftVersion("1.21.2");
|
||||
|
||||
/**
|
||||
* Version 1.21.0 - the tricky trials update
|
||||
*/
|
||||
@@ -149,7 +199,7 @@ public final class MinecraftVersion implements Comparable<MinecraftVersion>, Ser
|
||||
/**
|
||||
* The latest release version of minecraft.
|
||||
*/
|
||||
public static final MinecraftVersion LATEST = v1_21_0;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.comphenix.protocol.utility;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufProcessor;
|
||||
import io.netty.util.ByteProcessor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.GatheringByteChannel;
|
||||
import java.nio.channels.ScatteringByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -54,6 +56,16 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf asReadOnly() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readerIndex() {
|
||||
return 0;
|
||||
@@ -179,36 +191,71 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShortLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShort(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedShortLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMedium(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMediumLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMedium(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnsignedMediumLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedInt(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUnsignedIntLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLongLE(int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getChar(int i) {
|
||||
return 0;
|
||||
@@ -264,6 +311,16 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int i, FileChannel fileChannel, long l, int i1) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharSequence(int i, int i1, Charset charset) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBoolean(int i, boolean b) {
|
||||
return null;
|
||||
@@ -279,21 +336,41 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setShortLE(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMedium(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setMediumLE(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setInt(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setIntLE(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLong(int i, long l) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setLongLE(int i, long l) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setChar(int i, int i1) {
|
||||
return null;
|
||||
@@ -349,11 +426,21 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int i, FileChannel fileChannel, long l, int i1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setZero(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setCharSequence(int i, CharSequence charSequence, Charset charset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
return false;
|
||||
@@ -374,36 +461,71 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShortLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShortLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMedium() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readMediumLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedMedium() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedMediumLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readIntLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readUnsignedInt() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readUnsignedIntLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLongLE() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() {
|
||||
return 0;
|
||||
@@ -429,6 +551,11 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readRetainedSlice(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf readBytes(ByteBuf byteBuf) {
|
||||
return null;
|
||||
@@ -469,6 +596,16 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence readCharSequence(int i, Charset charset) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(FileChannel fileChannel, long l, int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf skipBytes(int i) {
|
||||
return null;
|
||||
@@ -489,21 +626,41 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeShortLE(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeMedium(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeMediumLE(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeInt(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeIntLE(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLong(long l) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeLongLE(long l) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeChar(int i) {
|
||||
return null;
|
||||
@@ -559,11 +716,21 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeBytes(FileChannel fileChannel, long l, int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf writeZero(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int writeCharSequence(CharSequence charSequence, Charset charset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(int i, int i1, byte b) {
|
||||
return 0;
|
||||
@@ -585,22 +752,22 @@ public class ZeroBuffer extends ByteBuf {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(ByteBufProcessor byteBufProcessor) {
|
||||
public int forEachByte(ByteProcessor byteProcessor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByte(int i, int i1, ByteBufProcessor byteBufProcessor) {
|
||||
public int forEachByte(int i, int i1, ByteProcessor byteProcessor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(ByteBufProcessor byteBufProcessor) {
|
||||
public int forEachByteDesc(ByteProcessor byteProcessor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int forEachByteDesc(int i, int i1, ByteBufProcessor byteBufProcessor) {
|
||||
public int forEachByteDesc(int i, int i1, ByteProcessor byteProcessor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -619,16 +786,31 @@ public class ZeroBuffer extends ByteBuf {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retainedSlice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf slice(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retainedSlice(int i, int i1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf duplicate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retainedDuplicate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 0;
|
||||
@@ -738,4 +920,14 @@ public class ZeroBuffer extends ByteBuf {
|
||||
public ByteBuf retain() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf touch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf touch(Object o) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
@@ -873,6 +874,13 @@ public class BukkitConverters {
|
||||
return ignoreNull(new EquivalentConverter<EntityType>() {
|
||||
@Override
|
||||
public Object getGeneric(EntityType specific) {
|
||||
if (MinecraftVersion.v26_2.atOrAbove()) {
|
||||
// EntityType.byString was removed in 26.2, resolve through the registry instead
|
||||
WrappedRegistry registry = WrappedRegistry.getRegistry(MinecraftReflection.getEntityTypes());
|
||||
NamespacedKey key = specific.getKey();
|
||||
return registry.get(new MinecraftKey(key.getNamespace(), key.getKey()));
|
||||
}
|
||||
|
||||
if (entityTypeFromName == null) {
|
||||
Class<?> entityTypesClass = MinecraftReflection.getEntityTypes();
|
||||
entityTypeFromName = Accessors.getMethodAccessor(
|
||||
@@ -1428,6 +1436,142 @@ public class BukkitConverters {
|
||||
});
|
||||
}
|
||||
|
||||
// Lazily-resolved CraftDamageType reflection. volatile + DCL keeps the publish safe
|
||||
// and avoids re-running fuzzy lookups in concurrent injection paths.
|
||||
private static volatile MethodAccessor damageTypeToNms = null;
|
||||
private static volatile MethodAccessor damageTypeFromNms = null;
|
||||
private static 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);
|
||||
}
|
||||
|
||||
private static MethodAccessor resolveDamageTypeToNms() {
|
||||
MethodAccessor accessor = damageTypeToNms;
|
||||
if (accessor != null) return accessor;
|
||||
synchronized (BukkitConverters.class) {
|
||||
accessor = damageTypeToNms;
|
||||
if (accessor != null) return accessor;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodAccessor resolveDamageTypeFromNms() {
|
||||
MethodAccessor accessor = damageTypeFromNms;
|
||||
if (accessor != null) return accessor;
|
||||
synchronized (BukkitConverters.class) {
|
||||
accessor = damageTypeFromNms;
|
||||
if (accessor != null) return accessor;
|
||||
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>() {
|
||||
|
||||
@Override
|
||||
public Class<org.bukkit.damage.DamageType> getSpecificType() {
|
||||
return org.bukkit.damage.DamageType.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneric(org.bukkit.damage.DamageType 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) {
|
||||
MethodAccessor accessor = resolveDamageTypeFromNms();
|
||||
if (damageTypeFromNmsAcceptsHolder) {
|
||||
generic = getDamageTypeHolder(generic);
|
||||
}
|
||||
return (org.bukkit.damage.DamageType) accessor.invoke(null, generic);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Class<?> dimensionManager;
|
||||
private static FauxEnumConverter<Dimension> dimensionConverter;
|
||||
private static FauxEnumConverter<DimensionImpl> dimensionImplConverter;
|
||||
|
||||
@@ -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,7 +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_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 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;
|
||||
|
||||
@@ -55,22 +66,73 @@ public final class CustomPacketPayloadWrapper {
|
||||
MINECRAFT_KEY_CLASS = MinecraftReflection.getMinecraftKeyClass();
|
||||
CUSTOM_PACKET_PAYLOAD_CLASS = MinecraftReflection.getMinecraftClass("network.protocol.common.custom.CustomPacketPayload");
|
||||
|
||||
Method getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeExact(MINECRAFT_KEY_CLASS)
|
||||
.parameterCount(0)
|
||||
.build());
|
||||
GET_ID_PAYLOAD_METHOD = Accessors.getMethodAccessor(getPayloadId);
|
||||
Method getPayloadId = null;
|
||||
Method getPayloadIdHolder = null;
|
||||
Method getPayloadIdFromHolder = null;
|
||||
boolean useDiscardedPayload = false;
|
||||
try {
|
||||
getPayloadId = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeExact(MINECRAFT_KEY_CLASS)
|
||||
.parameterCount(0)
|
||||
.build());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// 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_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;
|
||||
|
||||
Method serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.parameterCount(1)
|
||||
.parameterDerivedOf(ByteBuf.class, 0)
|
||||
.build());
|
||||
SERIALIZE_PAYLOAD_METHOD = Accessors.getMethodAccessor(serializePayloadData);
|
||||
Method serializePayloadData = null;
|
||||
try {
|
||||
serializePayloadData = FuzzyReflection.fromClass(CUSTOM_PACKET_PAYLOAD_CLASS).getMethod(FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.returnTypeVoid()
|
||||
.parameterCount(1)
|
||||
.parameterDerivedOf(ByteBuf.class, 0)
|
||||
.build());
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// 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 = makePayloadWrapper();
|
||||
Constructor<?> payloadWrapperConstructor;
|
||||
boolean discardedPayloadUsesByteArray = false;
|
||||
if (useDiscardedPayload) {
|
||||
Class<?> discardedPayloadClass = MinecraftReflection.getMinecraftClass(
|
||||
"network.protocol.common.custom.DiscardedPayload");
|
||||
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>() {
|
||||
@@ -94,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)
|
||||
@@ -116,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 ======
|
||||
|
||||
/**
|
||||
@@ -171,29 +326,39 @@ public final class CustomPacketPayloadWrapper {
|
||||
* @return a wrapper holding the minecraft key and payload of the given custom payload instance.
|
||||
*/
|
||||
public static CustomPacketPayloadWrapper fromUnknownPayload(Object payload) {
|
||||
Object messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
|
||||
MinecraftKey id = MinecraftKey.getConverter().getSpecific(messageId);
|
||||
MinecraftKey id = getPayloadId(payload);
|
||||
|
||||
// we read and retain the underlying buffer in case the class uses a buffer to store the data
|
||||
// this way, when passing the packet to further handling, the buffer is not released and can be re-used
|
||||
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(() -> {
|
||||
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);
|
||||
}
|
||||
|
||||
private static MinecraftKey getPayloadId(Object payload) {
|
||||
Object messageId;
|
||||
if (GET_ID_PAYLOAD_METHOD != null) {
|
||||
messageId = GET_ID_PAYLOAD_METHOD.invoke(payload);
|
||||
} else {
|
||||
Object idHolder = GET_ID_HOLDER_PAYLOAD_METHOD.invoke(payload);
|
||||
messageId = GET_ID_PAYLOAD_HOLDER_METHOD.invoke(idHolder);
|
||||
}
|
||||
return MinecraftKey.getConverter().getSpecific(messageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the generic id of the wrapped message id.
|
||||
*
|
||||
@@ -230,6 +395,13 @@ public final class CustomPacketPayloadWrapper {
|
||||
* @return a new payload wrapper instance using the provided message id and payload.
|
||||
*/
|
||||
public Object newHandle() {
|
||||
if (USE_DISCARDED_PAYLOAD) {
|
||||
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);
|
||||
}
|
||||
return PAYLOAD_WRAPPER_CONSTRUCTOR.invoke(this.getGenericId(), this.payload);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,12 @@ public abstract class EnumWrappers {
|
||||
public enum ClientCommand {
|
||||
PERFORM_RESPAWN,
|
||||
REQUEST_STATS,
|
||||
REQUEST_GAMERULE_VALUES,
|
||||
|
||||
/**
|
||||
* @deprecated Removed in 26.1
|
||||
*/
|
||||
@Deprecated
|
||||
OPEN_INVENTORY_ACHIEVEMENT
|
||||
}
|
||||
|
||||
@@ -140,6 +146,8 @@ public abstract class EnumWrappers {
|
||||
UPDATE_LISTED,
|
||||
UPDATE_LATENCY,
|
||||
UPDATE_DISPLAY_NAME,
|
||||
UPDATE_LIST_ORDER,
|
||||
UPDATE_HAT,
|
||||
/**
|
||||
* @deprecated Removed in 1.19.3
|
||||
*/
|
||||
@@ -178,7 +186,9 @@ public abstract class EnumWrappers {
|
||||
DROP_ALL_ITEMS,
|
||||
DROP_ITEM,
|
||||
RELEASE_USE_ITEM,
|
||||
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND");
|
||||
SWAP_HELD_ITEMS("SWAP_ITEM_WITH_OFFHAND"),
|
||||
STAB,
|
||||
CHANGE_DESTROY_DIRECTION;
|
||||
|
||||
final String[] aliases;
|
||||
PlayerDigType(String... aliases) {
|
||||
@@ -191,8 +201,18 @@ public abstract class EnumWrappers {
|
||||
}
|
||||
|
||||
public enum PlayerAction implements AliasedEnum {
|
||||
/**
|
||||
* @deprecated Removed in 1.21.6
|
||||
*/
|
||||
@Deprecated
|
||||
START_SNEAKING("PRESS_SHIFT_KEY"),
|
||||
|
||||
/**
|
||||
* @deprecated Removed in 1.21.6
|
||||
*/
|
||||
@Deprecated
|
||||
STOP_SNEAKING("RELEASE_SHIFT_KEY"),
|
||||
|
||||
STOP_SLEEPING,
|
||||
START_SPRINTING,
|
||||
STOP_SPRINTING,
|
||||
@@ -332,7 +352,8 @@ public abstract class EnumWrappers {
|
||||
NEUTRAL("neutral"),
|
||||
PLAYERS("player"),
|
||||
AMBIENT("ambient"),
|
||||
VOICE("voice");
|
||||
VOICE("voice"),
|
||||
UI("ui");
|
||||
|
||||
private static final Map<String, SoundCategory> LOOKUP;
|
||||
static {
|
||||
@@ -364,7 +385,8 @@ public abstract class EnumWrappers {
|
||||
LEGS,
|
||||
CHEST,
|
||||
HEAD,
|
||||
BODY
|
||||
BODY,
|
||||
SADDLE
|
||||
}
|
||||
|
||||
public enum Hand {
|
||||
@@ -638,6 +660,71 @@ public abstract class EnumWrappers {
|
||||
TRANSFER;
|
||||
}
|
||||
|
||||
public enum TeamCollisionRule {
|
||||
|
||||
ALWAYS("always"),
|
||||
NEVER("never"),
|
||||
PUSH_OTHER_TEAMS("pushOtherTeams"),
|
||||
PUSH_OWN_TEAM("pushOwnTeam");
|
||||
|
||||
public static TeamCollisionRule fromName(String name) {
|
||||
for (TeamCollisionRule value : values()) {
|
||||
if (value.name.equals(name)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown team collision rule: " + name);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
TeamCollisionRule(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TeamVisibility {
|
||||
|
||||
ALWAYS("always"),
|
||||
NEVER("never"),
|
||||
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
|
||||
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
|
||||
|
||||
public static TeamVisibility fromName(String name) {
|
||||
for (TeamVisibility value : values()) {
|
||||
if (value.name.equals(name)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown team visibility: " + name);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
|
||||
TeamVisibility(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public enum HeightmapType {
|
||||
WORLD_SURFACE_WG,
|
||||
WORLD_SURFACE,
|
||||
OCEAN_FLOOR_WG,
|
||||
OCEAN_FLOOR,
|
||||
MOTION_BLOCKING,
|
||||
MOTION_BLOCKING_NO_LEAVES,
|
||||
}
|
||||
|
||||
private static Class<?> PROTOCOL_CLASS = null;
|
||||
private static Class<?> CLIENT_COMMAND_CLASS = null;
|
||||
private static Class<?> CHAT_VISIBILITY_CLASS = null;
|
||||
@@ -663,6 +750,9 @@ public abstract class EnumWrappers {
|
||||
private static Class<?> RENDER_TYPE_CLASS = null;
|
||||
private static Class<?> CHAT_FORMATTING_CLASS = null;
|
||||
private static Class<?> CLIENT_INTENT_CLASS = null;
|
||||
private static Class<?> TEAM_COLLISION_RULE_CLASS = null;
|
||||
private static Class<?> TEAM_VISIBILITY_CLASS = null;
|
||||
private static Class<?> HEIGHTMAP_TYPE_CLASS = null;
|
||||
|
||||
private static boolean INITIALIZING = false;
|
||||
private static boolean INITIALIZED = false;
|
||||
@@ -714,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) {
|
||||
@@ -723,7 +816,7 @@ public abstract class EnumWrappers {
|
||||
}
|
||||
|
||||
try {
|
||||
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
|
||||
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory", "sounds.SoundSource");
|
||||
} catch (Exception ex) {
|
||||
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
|
||||
}
|
||||
@@ -739,13 +832,17 @@ public abstract class EnumWrappers {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
|
||||
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.parameterCount(0)
|
||||
.returnTypeMatches(FuzzyMatchers.except(Void.class))
|
||||
.build());
|
||||
if (MinecraftVersion.v26_1.atOrAbove()) {
|
||||
// TODO
|
||||
} else {
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
|
||||
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.parameterCount(0)
|
||||
.returnTypeMatches(FuzzyMatchers.except(Void.class))
|
||||
.build());
|
||||
|
||||
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
|
||||
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
|
||||
}
|
||||
} else {
|
||||
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
|
||||
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
|
||||
@@ -770,6 +867,17 @@ public abstract class EnumWrappers {
|
||||
|
||||
CLIENT_INTENT_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0);
|
||||
|
||||
TEAM_COLLISION_RULE_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush" /* Spigot Mapping */,
|
||||
"world.scores.Team$CollisionRule" /* Mojang Mapping */);
|
||||
TEAM_VISIBILITY_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility" /* Spigot Mapping */,
|
||||
"world.scores.Team$Visibility" /* Mojang Mapping */);
|
||||
|
||||
HEIGHTMAP_TYPE_CLASS = MinecraftReflection.getNullableNMS(
|
||||
"world.level.levelgen.HeightMap$Type" /* Spigot Mapping */,
|
||||
"world.level.levelgen.Heightmap$Types" /* Mojang Mapping */);
|
||||
|
||||
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
|
||||
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
|
||||
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
|
||||
@@ -794,6 +902,9 @@ public abstract class EnumWrappers {
|
||||
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
|
||||
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
|
||||
associate(CLIENT_INTENT_CLASS, ClientIntent.class, getClientIntentConverter());
|
||||
associate(TEAM_COLLISION_RULE_CLASS, TeamCollisionRule.class, getTeamCollisionRuleConverter());
|
||||
associate(TEAM_VISIBILITY_CLASS, TeamVisibility.class, getTeamVisibilityConverter());
|
||||
associate(HEIGHTMAP_TYPE_CLASS, HeightmapType.class, getHeightmapTypeConverter());
|
||||
|
||||
if (ENTITY_POSE_CLASS != null) {
|
||||
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
|
||||
@@ -968,6 +1079,21 @@ public abstract class EnumWrappers {
|
||||
return CLIENT_INTENT_CLASS;
|
||||
}
|
||||
|
||||
public static Class<?> getTeamCollisionRuleClass() {
|
||||
initialize();
|
||||
return TEAM_COLLISION_RULE_CLASS;
|
||||
}
|
||||
|
||||
public static Class<?> getTeamVisibilityClass() {
|
||||
initialize();
|
||||
return TEAM_VISIBILITY_CLASS;
|
||||
}
|
||||
|
||||
public static Class<?> getHeightmapTypeClass() {
|
||||
initialize();
|
||||
return HEIGHTMAP_TYPE_CLASS;
|
||||
}
|
||||
|
||||
// Get the converters
|
||||
public static EquivalentConverter<Protocol> getProtocolConverter() {
|
||||
return new EnumConverter<>(getProtocolClass(), Protocol.class);
|
||||
@@ -1065,6 +1191,18 @@ public abstract class EnumWrappers {
|
||||
return new EnumConverter<>(getClientIntentClass(), ClientIntent.class);
|
||||
}
|
||||
|
||||
public static EquivalentConverter<TeamCollisionRule> getTeamCollisionRuleConverter() {
|
||||
return new EnumConverter<>(getTeamCollisionRuleClass(), TeamCollisionRule.class);
|
||||
}
|
||||
|
||||
public static EquivalentConverter<TeamVisibility> getTeamVisibilityConverter() {
|
||||
return new EnumConverter<>(getTeamVisibilityClass(), TeamVisibility.class);
|
||||
}
|
||||
|
||||
public static EquivalentConverter<HeightmapType> getHeightmapTypeConverter() {
|
||||
return new EnumConverter<>(getHeightmapTypeClass(), HeightmapType.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() {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
|
||||
/**
|
||||
* Mutable version of PropertyMap
|
||||
* Credit to PaperMC: https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/io/papermc/paper/profile/MutablePropertyMap.java
|
||||
*/
|
||||
public class MutablePropertyMap extends PropertyMap {
|
||||
private final Multimap<String, Property> properties = HashMultimap.create();
|
||||
|
||||
public MutablePropertyMap() {
|
||||
super(ImmutableMultimap.of());
|
||||
}
|
||||
|
||||
public MutablePropertyMap(final Multimap<String, Property> properties) {
|
||||
this();
|
||||
this.putAll(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Multimap<String, Property> delegate() {
|
||||
return this.properties;
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,12 @@
|
||||
*/
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
@@ -29,8 +29,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
|
||||
/**
|
||||
* Represents an immutable PlayerInfoData in the PLAYER_INFO packet.
|
||||
@@ -41,7 +40,9 @@ public class PlayerInfoData {
|
||||
|
||||
private final UUID profileId;
|
||||
private final int latency;
|
||||
private final int listOrder;
|
||||
private final boolean listed;
|
||||
private final boolean showHat;
|
||||
private final NativeGameMode gameMode;
|
||||
private final WrappedGameProfile profile;
|
||||
private final WrappedChatComponent displayName;
|
||||
@@ -74,6 +75,21 @@ public class PlayerInfoData {
|
||||
this(profileId, latency, listed, gameMode, profile, displayName, (WrappedRemoteChatSessionData) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new PlayerInfoData for Minecraft 1.19.3 or later.
|
||||
*
|
||||
* @param profileId the id of the profile (has to be non-null)
|
||||
* @param latency the latency in milliseconds
|
||||
* @param listed whether the player is listed in the tab list
|
||||
* @param gameMode the game mode
|
||||
* @param profile the game profile
|
||||
* @param displayName display name in tab list (optional)
|
||||
* @param remoteChatSession the remote chat session for this profile or null
|
||||
*/
|
||||
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
|
||||
this(profileId, latency, listed, gameMode, profile, displayName, false, remoteChatSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new PlayerInfoData for Minecraft 1.19. This is incompatible on 1.19.3.
|
||||
* @see PlayerInfoData#PlayerInfoData(UUID, int, boolean, NativeGameMode, WrappedGameProfile, WrappedChatComponent, WrappedRemoteChatSessionData)
|
||||
@@ -96,10 +112,12 @@ public class PlayerInfoData {
|
||||
this.displayName = displayName;
|
||||
this.profileKeyData = profileKeyData;
|
||||
this.remoteChatSessionData = null;
|
||||
this.showHat = false;
|
||||
this.listOrder = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new PlayerInfoData for Minecraft 1.19.3 or later.
|
||||
* Constructs a new PlayerInfoData for Minecraft 1.21.2 or later.
|
||||
*
|
||||
* @param profileId the id of the profile (has to be non-null)
|
||||
* @param latency the latency in milliseconds
|
||||
@@ -107,9 +125,43 @@ public class PlayerInfoData {
|
||||
* @param gameMode the game mode
|
||||
* @param profile the game profile
|
||||
* @param displayName display name in tab list (optional)
|
||||
* @param listOrder the priority of this entry in the tab list
|
||||
* @param remoteChatSession the remote chat session for this profile or null
|
||||
*/
|
||||
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
|
||||
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, int listOrder, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
|
||||
this(profileId, latency, listed, gameMode, profile, displayName, false, listOrder, remoteChatSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new PlayerInfoData for Minecraft 1.21.4 or later.
|
||||
*
|
||||
* @param profileId the id of the profile (has to be non-null)
|
||||
* @param latency the latency in milliseconds
|
||||
* @param listed whether the player is listed in the tab list
|
||||
* @param gameMode the game mode
|
||||
* @param profile the game profile
|
||||
* @param displayName display name in tab list (optional)
|
||||
* @param showHat whether a hat should be shown
|
||||
* @param remoteChatSession the remote chat session for this profile or null
|
||||
*/
|
||||
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, boolean showHat, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
|
||||
this(profileId, latency, listed, gameMode, profile, displayName, showHat, 0, remoteChatSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new PlayerInfoData for Minecraft 1.21.4 or later.
|
||||
*
|
||||
* @param profileId the id of the profile (has to be non-null)
|
||||
* @param latency the latency in milliseconds
|
||||
* @param listed whether the player is listed in the tab list
|
||||
* @param gameMode the game mode
|
||||
* @param profile the game profile
|
||||
* @param displayName display name in tab list (optional)
|
||||
* @param showHat whether a hat should be shown
|
||||
* @param listOrder the priority of this entry in the tab list
|
||||
* @param remoteChatSession the remote chat session for this profile or null
|
||||
*/
|
||||
public PlayerInfoData(UUID profileId, int latency, boolean listed, NativeGameMode gameMode, WrappedGameProfile profile, WrappedChatComponent displayName, boolean showHat, int listOrder, @Nullable WrappedRemoteChatSessionData remoteChatSession) {
|
||||
this.profileId = profileId;
|
||||
this.latency = latency;
|
||||
this.listed = listed;
|
||||
@@ -118,6 +170,8 @@ public class PlayerInfoData {
|
||||
this.displayName = displayName;
|
||||
this.profileKeyData = null;
|
||||
this.remoteChatSessionData = remoteChatSession;
|
||||
this.showHat = showHat;
|
||||
this.listOrder = listOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,6 +233,22 @@ public class PlayerInfoData {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if a hat is shown (since 1.21.4)
|
||||
* @return if the hat is shown
|
||||
*/
|
||||
public boolean isShowHat() {
|
||||
return showHat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the priority of this entry in the tab list (since 1.21.2)
|
||||
* @return the priority of this entry in the tab list
|
||||
*/
|
||||
public int getListOrder() {
|
||||
return listOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public key of the profile (since 1.19). Returns the public key of the remote chat session since 1.19.3
|
||||
* @return The public key of the profile.
|
||||
@@ -202,7 +272,9 @@ public class PlayerInfoData {
|
||||
* @return A new converter.
|
||||
*/
|
||||
public static EquivalentConverter<PlayerInfoData> getConverter() {
|
||||
return new EquivalentConverter<PlayerInfoData>() {
|
||||
return new EquivalentConverter<>() {
|
||||
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
|
||||
|
||||
@Override
|
||||
public Object getGeneric(PlayerInfoData specific) {
|
||||
if (constructor == null) {
|
||||
@@ -225,13 +297,20 @@ public class PlayerInfoData {
|
||||
args.add(EnumWrappers.getGameModeClass());
|
||||
args.add(MinecraftReflection.getIChatBaseComponentClass());
|
||||
|
||||
if (MinecraftVersion.v1_21_4.atOrAbove()) {
|
||||
args.add(boolean.class);
|
||||
}
|
||||
if (MinecraftVersion.v1_21_2.atOrAbove()) {
|
||||
args.add(int.class);
|
||||
}
|
||||
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
args.add(MinecraftReflection.getRemoteChatSessionDataClass());
|
||||
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
|
||||
args.add(MinecraftReflection.getProfilePublicKeyDataClass());
|
||||
}
|
||||
|
||||
constructor = MinecraftReflection.getPlayerInfoDataClass().getConstructor(args.toArray(new Class<?>[0]));
|
||||
constructor = MinecraftReflection.getPlayerInfoDataClass().getConstructor(args.toArray(EMPTY_CLASS_ARRAY));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot find PlayerInfoData constructor.", e);
|
||||
}
|
||||
@@ -240,32 +319,72 @@ public class PlayerInfoData {
|
||||
// Attempt to construct the underlying PlayerInfoData
|
||||
|
||||
try {
|
||||
Object gameMode = EnumWrappers.getGameModeConverter().getGeneric(specific.gameMode);
|
||||
Object gameMode = Converters.ignoreNull(EnumWrappers.getGameModeConverter()).getGeneric(specific.gameMode);
|
||||
Object displayName = specific.displayName != null ? specific.displayName.handle : null;
|
||||
|
||||
Object profile = specific.profile != null ? specific.profile.handle : null;
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
return constructor.newInstance(
|
||||
specific.profileId,
|
||||
profile,
|
||||
specific.listed,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName,
|
||||
specific.remoteChatSessionData != null ? BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(specific.remoteChatSessionData) : null
|
||||
);
|
||||
Object remoteChatSessionData = specific.remoteChatSessionData != null ? BukkitConverters.getWrappedRemoteChatSessionDataConverter().getGeneric(specific.remoteChatSessionData) : null;
|
||||
|
||||
Object[] args;
|
||||
|
||||
if (MinecraftVersion.v1_21_4.atOrAbove()) {
|
||||
args = new Object[] {
|
||||
specific.profileId,
|
||||
profile,
|
||||
specific.listed,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName,
|
||||
specific.showHat,
|
||||
specific.listOrder,
|
||||
remoteChatSessionData
|
||||
};
|
||||
} else if (MinecraftVersion.v1_21_2.atOrAbove()) {
|
||||
args = new Object[] {
|
||||
specific.profileId,
|
||||
profile,
|
||||
specific.listed,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName,
|
||||
specific.listOrder,
|
||||
remoteChatSessionData
|
||||
};
|
||||
} else if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
args = new Object[] {
|
||||
specific.profileId,
|
||||
profile,
|
||||
specific.listed,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName,
|
||||
remoteChatSessionData
|
||||
};
|
||||
} else if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
|
||||
return constructor.newInstance(
|
||||
profile,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName,
|
||||
specific.profileKeyData == null ? null : specific.profileKeyData.handle);
|
||||
args = new Object[] {
|
||||
profile,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName,
|
||||
specific.profileKeyData == null ? null : specific.profileKeyData.handle
|
||||
};
|
||||
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
return constructor.newInstance(profile, specific.latency, gameMode, displayName);
|
||||
args = new Object[] {
|
||||
profile,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName
|
||||
};
|
||||
} else {
|
||||
return constructor.newInstance(null, profile, specific.latency, gameMode, displayName);
|
||||
args = new Object[] {
|
||||
null,
|
||||
profile,
|
||||
specific.latency,
|
||||
gameMode,
|
||||
displayName
|
||||
};
|
||||
}
|
||||
|
||||
return constructor.newInstance(args);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to construct PlayerInfoData.", e);
|
||||
}
|
||||
@@ -275,37 +394,60 @@ public class PlayerInfoData {
|
||||
public PlayerInfoData getSpecific(Object generic) {
|
||||
if (MinecraftReflection.isPlayerInfoData(generic)) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(generic.getClass(), null, false)
|
||||
.withTarget(generic);
|
||||
.withTarget(generic);
|
||||
|
||||
StructureModifier<WrappedGameProfile> gameProfiles = modifier.withType(
|
||||
MinecraftReflection.getGameProfileClass(), BukkitConverters.getWrappedGameProfileConverter());
|
||||
MinecraftReflection.getGameProfileClass(), BukkitConverters.getWrappedGameProfileConverter());
|
||||
WrappedGameProfile gameProfile = gameProfiles.read(0);
|
||||
|
||||
StructureModifier<Integer> ints = modifier.withType(int.class);
|
||||
int latency = ints.read(0);
|
||||
|
||||
StructureModifier<NativeGameMode> gameModes = modifier.withType(
|
||||
EnumWrappers.getGameModeClass(), EnumWrappers.getGameModeConverter());
|
||||
EnumWrappers.getGameModeClass(), Converters.ignoreNull(EnumWrappers.getGameModeConverter()));
|
||||
NativeGameMode gameMode = gameModes.read(0);
|
||||
|
||||
StructureModifier<WrappedChatComponent> displayNames = modifier.withType(
|
||||
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
|
||||
MinecraftReflection.getIChatBaseComponentClass(), BukkitConverters.getWrappedChatComponentConverter());
|
||||
WrappedChatComponent displayName = displayNames.read(0);
|
||||
|
||||
if(MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
if (MinecraftVersion.v1_21_4.atOrAbove()) {
|
||||
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
|
||||
latency,
|
||||
modifier.<Boolean>withType(boolean.class).read(0),
|
||||
gameMode,
|
||||
gameProfile,
|
||||
displayName,
|
||||
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
|
||||
);
|
||||
latency,
|
||||
modifier.<Boolean>withType(boolean.class).read(0),
|
||||
gameMode,
|
||||
gameProfile,
|
||||
displayName,
|
||||
modifier.<Boolean>withType(boolean.class).read(1),
|
||||
ints.read(1),
|
||||
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
|
||||
);
|
||||
}
|
||||
if (MinecraftVersion.v1_21_2.atOrAbove()) {
|
||||
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
|
||||
latency,
|
||||
modifier.<Boolean>withType(boolean.class).read(0),
|
||||
gameMode,
|
||||
gameProfile,
|
||||
displayName,
|
||||
ints.read(1),
|
||||
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
|
||||
);
|
||||
}
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
return new PlayerInfoData(modifier.<UUID>withType(UUID.class).read(0),
|
||||
latency,
|
||||
modifier.<Boolean>withType(boolean.class).read(0),
|
||||
gameMode,
|
||||
gameProfile,
|
||||
displayName,
|
||||
modifier.withType(MinecraftReflection.getRemoteChatSessionDataClass(), BukkitConverters.getWrappedRemoteChatSessionDataConverter()).read(0)
|
||||
);
|
||||
}
|
||||
WrappedProfileKeyData key = null;
|
||||
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
|
||||
StructureModifier<WrappedProfileKeyData> keyData = modifier.withType(
|
||||
MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter());
|
||||
MinecraftReflection.getProfilePublicKeyDataClass(), BukkitConverters.getWrappedPublicKeyDataConverter());
|
||||
key = keyData.read(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
|
||||
private static MethodAccessor TO_LEGACY_DATA;
|
||||
private static MethodAccessor GET_BLOCK;
|
||||
private static MethodAccessor BLOCK_FROM_MATERIAL;
|
||||
private static MethodAccessor GET_BLOCK_DATA;
|
||||
private static MethodAccessor DEFAULT_BLOCK_DATA;
|
||||
private static MethodAccessor FROM_LEGACY_DATA;
|
||||
private static MethodAccessor GET_HANDLE;
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
|
||||
.parameterCount(0)
|
||||
.returnTypeExact(IBLOCK_DATA)
|
||||
.build();
|
||||
GET_BLOCK_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
DEFAULT_BLOCK_DATA = Accessors.getMethodAccessor(fuzzy.getMethod(contract));
|
||||
|
||||
fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getCraftBukkitClass("block.data.CraftBlockData"));
|
||||
contract = FuzzyMethodContract
|
||||
@@ -131,7 +131,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
|
||||
@Override
|
||||
public void setType(Material material) {
|
||||
Object block = BLOCK_FROM_MATERIAL.invoke(null, material);
|
||||
setHandle(GET_BLOCK_DATA.invoke(block));
|
||||
setHandle(DEFAULT_BLOCK_DATA.invoke(block));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,7 +141,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
|
||||
|
||||
@Override
|
||||
public void setTypeAndData(Material material, int data) {
|
||||
setHandle(TO_LEGACY_DATA.invoke(null, material, (byte) data));
|
||||
setHandle(FROM_LEGACY_DATA.invoke(null, material, (byte) data));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,7 +151,7 @@ public abstract class WrappedBlockData extends AbstractWrapper implements Clonab
|
||||
|
||||
private static WrappedBlockData createNewData(Material material) {
|
||||
Object block = BLOCK_FROM_MATERIAL.invoke(null, material);
|
||||
return new NewBlockData(GET_BLOCK_DATA.invoke(block));
|
||||
return new NewBlockData(DEFAULT_BLOCK_DATA.invoke(block));
|
||||
}
|
||||
|
||||
private static WrappedBlockData createNewData(Material material, int data) {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.comphenix.protocol.wrappers.codecs.WrappedCodec;
|
||||
import com.comphenix.protocol.wrappers.codecs.WrappedDynamicOps;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
@@ -35,41 +40,51 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
|
||||
|
||||
private static ConstructorAccessor CONSTRUCT_TEXT_COMPONENT = null;
|
||||
|
||||
static {
|
||||
FuzzyReflection reflection = FuzzyReflection.fromClass(SERIALIZER, true);
|
||||
private static WrappedCodec CODEC;
|
||||
private static WrappedDynamicOps REGISTRY_JSON_OPS;
|
||||
|
||||
// Retrieve the correct methods
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(reflection.getMethod(FuzzyMethodContract.newBuilder()
|
||||
static {
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(SERIALIZER, true);
|
||||
|
||||
if (MinecraftVersion.v1_21_6.atOrAbove()) {
|
||||
Field codecField = fuzzy.getFieldByType("CODEC", MinecraftReflection.getCodecClass());
|
||||
CODEC = WrappedCodec.fromHandle(Accessors.getFieldAccessor(codecField).get(null));
|
||||
REGISTRY_JSON_OPS = MinecraftRegistryAccess.createSerializationContext(WrappedDynamicOps.json(false));
|
||||
} else if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.returnTypeExact(String.class)
|
||||
.parameterDerivedOf(COMPONENT)
|
||||
.parameterDerivedOf(MinecraftReflection.getHolderLookupProviderClass())
|
||||
.build()));
|
||||
} else {
|
||||
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(reflection.getMethodByReturnTypeAndParameters("serialize", /* a */
|
||||
SERIALIZE_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("serialize", /* a */
|
||||
String.class, new Class<?>[] { COMPONENT }));
|
||||
}
|
||||
|
||||
GSON = Accessors.getFieldAccessor(reflection.getFieldByType("gson", GSON_CLASS)).get(null);
|
||||
if (!MinecraftVersion.v1_20_4.atOrAbove()) {
|
||||
GSON = Accessors.getFieldAccessor(fuzzy.getFieldByType("gson", GSON_CLASS)).get(null);
|
||||
}
|
||||
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
DESERIALIZE = Accessors.getMethodAccessor(reflection.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.returnDerivedOf(COMPONENT)
|
||||
.parameterExactType(String.class)
|
||||
.parameterDerivedOf(MinecraftReflection.getHolderLookupProviderClass())
|
||||
.build()));
|
||||
} else if (MinecraftVersion.v1_20_4.atOrAbove()) {
|
||||
DESERIALIZE = Accessors.getMethodAccessor(reflection
|
||||
.getMethodByReturnTypeAndParameters("fromJson", MUTABLE_COMPONENT_CLASS.get(), new Class[] { String.class }));
|
||||
} else {
|
||||
try {
|
||||
DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getChatDeserializer(), true)
|
||||
.getMethodByReturnTypeAndParameters("deserialize", Object.class, new Class<?>[] { GSON_CLASS, String.class, Class.class, boolean.class }));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// We'll handle it in the ComponentParser
|
||||
DESERIALIZE = null;
|
||||
}
|
||||
}
|
||||
if (!MinecraftVersion.v1_21_6.atOrAbove()) {
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
DESERIALIZE = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.returnDerivedOf(COMPONENT)
|
||||
.parameterExactType(String.class)
|
||||
.parameterDerivedOf(MinecraftReflection.getHolderLookupProviderClass())
|
||||
.build()));
|
||||
} else if (MinecraftVersion.v1_20_4.atOrAbove()) {
|
||||
DESERIALIZE = Accessors.getMethodAccessor(fuzzy
|
||||
.getMethodByReturnTypeAndParameters("fromJson", MUTABLE_COMPONENT_CLASS.get(), new Class[]{String.class}));
|
||||
} else {
|
||||
try {
|
||||
DESERIALIZE = Accessors.getMethodAccessor(FuzzyReflection.fromClass(MinecraftReflection.getChatDeserializer(), true)
|
||||
.getMethodByReturnTypeAndParameters("deserialize", Object.class, new Class<?>[]{GSON_CLASS, String.class, Class.class, boolean.class}));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// We'll handle it in the ComponentParser
|
||||
DESERIALIZE = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get a component from a standard Minecraft message
|
||||
CONSTRUCT_COMPONENT = Accessors.getMethodAccessor(MinecraftReflection.getCraftChatMessage(), "fromString", String.class, boolean.class);
|
||||
@@ -83,6 +98,11 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
|
||||
}
|
||||
|
||||
private static Object serialize(Object handle) {
|
||||
if (CODEC != null) {
|
||||
Object jobj = CODEC.encode(handle, REGISTRY_JSON_OPS).getOrThrow(JsonParseException::new);
|
||||
return jobj.toString();
|
||||
}
|
||||
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
return SERIALIZE_COMPONENT.invoke(null, handle, MinecraftRegistryAccess.get());
|
||||
}
|
||||
@@ -91,6 +111,10 @@ public class WrappedChatComponent extends AbstractWrapper implements ClonableWra
|
||||
}
|
||||
|
||||
private static Object deserialize(String json) {
|
||||
if (CODEC != null) {
|
||||
return CODEC.parse(JsonParser.parseString(json), REGISTRY_JSON_OPS).getOrThrow(JsonParseException::new);
|
||||
}
|
||||
|
||||
if (MinecraftVersion.v1_20_5.atOrAbove()) {
|
||||
return DESERIALIZE.invoke(null, json, MinecraftRegistryAccess.get());
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -40,7 +41,6 @@ import com.comphenix.protocol.wrappers.EnumWrappers.EntityPose;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
@@ -66,7 +66,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
* Constructs a new DataWatcher wrapper around a NMS handle. The resulting
|
||||
* DataWatcher will likely have existing values that can be removed with
|
||||
* {@link #clear()}.
|
||||
*
|
||||
*
|
||||
* @param handle DataWatcher handle
|
||||
*/
|
||||
public WrappedDataWatcher(Object handle) {
|
||||
@@ -90,7 +90,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
* Constructs a new DataWatcher using a real entity. The resulting
|
||||
* DataWatcher will not have any keys or values and new ones will have to
|
||||
* be added using watcher objects.
|
||||
*
|
||||
*
|
||||
* @param entity The entity
|
||||
*/
|
||||
public WrappedDataWatcher(Entity entity) {
|
||||
@@ -102,7 +102,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Constructs a new DataWatcher using a fake egg entity and a given
|
||||
* list of watchable objects.
|
||||
*
|
||||
*
|
||||
* @param objects The list of objects
|
||||
*/
|
||||
public WrappedDataWatcher(List<WrappedWatchableObject> objects) {
|
||||
@@ -152,7 +152,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Gets the item at a given index.
|
||||
*
|
||||
*
|
||||
* @param index Index to get
|
||||
* @return The watchable object, or null if none exists
|
||||
*/
|
||||
@@ -170,7 +170,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Removes the item at a given index.
|
||||
*
|
||||
*
|
||||
* @param index Index to remove
|
||||
* @return The previous value, or null if none existed
|
||||
*/
|
||||
@@ -180,7 +180,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Whether or not this DataWatcher has an object at a given index.
|
||||
*
|
||||
*
|
||||
* @param index Index to check for
|
||||
* @return True if it does, false if not
|
||||
*/
|
||||
@@ -209,7 +209,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Get a watched byte.
|
||||
*
|
||||
*
|
||||
* @param index - index of the watched byte.
|
||||
* @return The watched byte, or NULL if this value doesn't exist.
|
||||
*/
|
||||
@@ -225,7 +225,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Get a watched short.
|
||||
*
|
||||
*
|
||||
* @param index - index of the watched short.
|
||||
* @return The watched short, or NULL if this value doesn't exist.
|
||||
*/
|
||||
@@ -237,7 +237,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Get a watched integer.
|
||||
*
|
||||
*
|
||||
* @param index - index of the watched integer.
|
||||
* @return The watched integer, or NULL if this value doesn't exist.
|
||||
*/
|
||||
@@ -263,7 +263,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Get a watched float.
|
||||
*
|
||||
*
|
||||
* @param index - index of the watched float.
|
||||
* @return The watched float, or NULL if this value doesn't exist.
|
||||
*/
|
||||
@@ -279,7 +279,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
*
|
||||
*
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
*/
|
||||
@@ -315,7 +315,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
*
|
||||
*
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
*/
|
||||
@@ -477,7 +477,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by index.
|
||||
*
|
||||
*
|
||||
* @param index Index of the object to retrieve.
|
||||
* @return The watched object or null if it doesn't exist.
|
||||
*/
|
||||
@@ -487,7 +487,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by watcher object.
|
||||
*
|
||||
*
|
||||
* @param object The watcher object
|
||||
* @return The watched object or null if it doesn't exist.
|
||||
*/
|
||||
@@ -500,11 +500,11 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Sets the DataWatcher Item at a given index to a new value. In 1.9 and up,
|
||||
* you cannot register objects without a watcher object.
|
||||
*
|
||||
*
|
||||
* @param index Index of the object to set
|
||||
* @param value New value
|
||||
* @param update Whether or not to inform the client
|
||||
*
|
||||
*
|
||||
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object, boolean)
|
||||
* @throws IllegalArgumentException in 1.9 and up if there isn't already an
|
||||
* object at this index
|
||||
@@ -526,12 +526,12 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Sets the DataWatcher Item at a given index to a new value.
|
||||
*
|
||||
*
|
||||
* @param index Index of the object to set
|
||||
* @param serializer Serializer from {@link Registry#get(Class)}
|
||||
* @param value New value
|
||||
* @param update Whether or not to inform the client
|
||||
*
|
||||
*
|
||||
* @see WrappedDataWatcher#setObject(WrappedDataWatcherObject, Object)
|
||||
*/
|
||||
public void setObject(int index, Serializer serializer, Object value, boolean update) {
|
||||
@@ -547,11 +547,11 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Sets the DataWatcher Item at a given index to a new value.
|
||||
*
|
||||
*
|
||||
* @param index Index of the object to set
|
||||
* @param value New value
|
||||
* @param update Whether or not to inform the client
|
||||
*
|
||||
*
|
||||
* @see WrappedDataWatcher#setObject(int, Object, boolean)
|
||||
*/
|
||||
public void setObject(int index, WrappedWatchableObject value, boolean update) {
|
||||
@@ -567,11 +567,11 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Sets the DataWatcher Item associated with a given watcher object to a new value.
|
||||
*
|
||||
*
|
||||
* @param object Associated watcher object
|
||||
* @param value Wrapped value
|
||||
* @param update Whether or not to inform the client
|
||||
*
|
||||
*
|
||||
* @see #setObject(WrappedDataWatcherObject, Object)
|
||||
*/
|
||||
public void setObject(WrappedDataWatcherObject object, WrappedWatchableObject value, boolean update) {
|
||||
@@ -589,10 +589,10 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
* Sets the DataWatcher Item associated with a given watcher object to a
|
||||
* new value. If there is not already an object at this index, the
|
||||
* specified watcher object must have a serializer.
|
||||
*
|
||||
*
|
||||
* @param object Associated watcher object
|
||||
* @param value New value
|
||||
*
|
||||
*
|
||||
* @throws IllegalArgumentException If the watcher object is null or must
|
||||
* have a serializer and does not have one.
|
||||
*/
|
||||
@@ -611,7 +611,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Clone the content of the current DataWatcher.
|
||||
*
|
||||
*
|
||||
* @return A cloned data watcher.
|
||||
*/
|
||||
public WrappedDataWatcher deepClone() {
|
||||
@@ -628,7 +628,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Retrieve the data watcher associated with an entity.
|
||||
*
|
||||
*
|
||||
* @param entity - the entity to read from.
|
||||
* @return Associated data watcher.
|
||||
*/
|
||||
@@ -682,7 +682,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Retrieves the type ID associated with a given class. No longer supported
|
||||
* in 1.9 and up due to the removal of type IDs.
|
||||
*
|
||||
*
|
||||
* @param clazz Class to find ID for
|
||||
* @return The ID, or null if not found
|
||||
*/
|
||||
@@ -693,7 +693,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Retrieves the class associated with a given type ID. No longer
|
||||
* supported in 1.9 and up due to the removal of type IDs.
|
||||
*
|
||||
*
|
||||
* @param typeID ID to find Class for
|
||||
* @return The Class, or null if not found
|
||||
*/
|
||||
@@ -729,7 +729,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Represents a DataWatcherObject in 1.9. In order to register an object,
|
||||
* the serializer must be specified.
|
||||
*
|
||||
*
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public static class WrappedDataWatcherObject {
|
||||
@@ -745,7 +745,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Creates a new watcher object from a NMS handle.
|
||||
*
|
||||
*
|
||||
* @param handle The handle
|
||||
*/
|
||||
public WrappedDataWatcherObject(Object handle) {
|
||||
@@ -755,7 +755,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Creates a new watcher object from an index and serializer.
|
||||
*
|
||||
*
|
||||
* @param index Index
|
||||
* @param serializer Serializer, see {@link Registry}
|
||||
*/
|
||||
@@ -795,7 +795,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Gets this watcher object's index.
|
||||
*
|
||||
*
|
||||
* @return The index
|
||||
*/
|
||||
public int getIndex() {
|
||||
@@ -805,7 +805,7 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Gets this watcher object's serializer. Will return null if the
|
||||
* serializer was never specified.
|
||||
*
|
||||
*
|
||||
* @return The serializer, or null
|
||||
*/
|
||||
public Serializer getSerializer() {
|
||||
@@ -911,23 +911,36 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
/**
|
||||
* Represents a DataWatcherSerializer in 1.9. If a Serializer is optional,
|
||||
* values must be wrapped in a {@link Optional}.
|
||||
*
|
||||
*
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public static class Serializer extends AbstractWrapper {
|
||||
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getDataWatcherSerializerClass();
|
||||
|
||||
private final Class<?> type;
|
||||
private final Type type;
|
||||
private final boolean optional;
|
||||
|
||||
/**
|
||||
* Constructs a new Serializer
|
||||
*
|
||||
*
|
||||
* @param type Type it serializes
|
||||
* @param handle NMS handle
|
||||
* @param optional Whether or not it's {@link Optional}
|
||||
* @deprecated Use {@link Serializer#Serializer(Type, Object, boolean)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public Serializer(Class<?> type, Object handle, boolean optional) {
|
||||
this((Type) type, handle, optional);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Serializer
|
||||
*
|
||||
* @param type Type it serializes
|
||||
* @param handle NMS handle
|
||||
* @param optional Whether or not it's {@link Optional}
|
||||
*/
|
||||
public Serializer(Class<?> type, Object handle, boolean optional) {
|
||||
public Serializer(Type type, Object handle, boolean optional) {
|
||||
super(HANDLE_TYPE);
|
||||
this.type = type;
|
||||
this.optional = optional;
|
||||
@@ -937,17 +950,32 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
|
||||
/**
|
||||
* Gets the type this serializer serializes.
|
||||
*
|
||||
*
|
||||
* @return The type
|
||||
* @deprecated use {@link #getGenericType()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
if (this.type instanceof Class<?> c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Serializer does not serialize a class");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type this serializer serializes.
|
||||
*
|
||||
* @return The type.
|
||||
*/
|
||||
public Type getGenericType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this serializer is optional, that is whether or not
|
||||
* the return type is wrapped in a {@link Optional}.
|
||||
*
|
||||
*
|
||||
* @return True if it is, false if not
|
||||
*/
|
||||
public boolean isOptional() {
|
||||
@@ -986,11 +1014,19 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
public static class Registry {
|
||||
private static boolean INITIALIZED = false;
|
||||
|
||||
private static Map<Class<?>, Serializer> RAW_REGISTRY = null;
|
||||
private static Map<Class<?>, Serializer> OPTIONAL_REGISTRY = null;
|
||||
private static Map<Type, Serializer> RAW_REGISTRY = null;
|
||||
private static Map<Type, Serializer> OPTIONAL_REGISTRY = null;
|
||||
|
||||
/**
|
||||
* Gets the first serializer associated with a given class.
|
||||
* @deprecated use {@link #get(Type)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static Serializer get(Class<?> clazz) {
|
||||
return get((Type) clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first serializer associated with a given type.
|
||||
*
|
||||
* <p><b>Note</b>: If {@link Serializer#isOptional() the serializer is optional},
|
||||
* values <i>must</i> be wrapped in an {@link Optional}.</p>
|
||||
@@ -998,39 +1034,46 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
* <p>If there are multiple serializers for a given class (i.e. BlockPosition),
|
||||
* you should use {@link #get(Class, boolean)} for more precision.</p>
|
||||
*
|
||||
* @param clazz Class to find serializer for
|
||||
* @param type Type to find serializer for
|
||||
* @return The serializer, or null if none exists
|
||||
*/
|
||||
public static Serializer get(Class<?> clazz) {
|
||||
Validate.notNull(clazz,"Class cannot be null!");
|
||||
public static Serializer get(Type type) {
|
||||
Validate.notNull(type,"Type cannot be null!");
|
||||
initialize();
|
||||
|
||||
Serializer serializer = RAW_REGISTRY.getOrDefault(clazz,
|
||||
OPTIONAL_REGISTRY.getOrDefault(clazz, null));
|
||||
Serializer serializer = RAW_REGISTRY.getOrDefault(type, OPTIONAL_REGISTRY.get(type));
|
||||
if (serializer == null) {
|
||||
throw new IllegalArgumentException("No serializer found for " + clazz);
|
||||
throw new IllegalArgumentException("No serializer found for " + type);
|
||||
}
|
||||
|
||||
return serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first serializer associated with a given class and optional state.
|
||||
* @deprecated use {@link #get(Type, boolean)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public static Serializer get(Class<?> clazz, boolean optional) {
|
||||
return get((Type) clazz, optional);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first serializer associated with a given type and optional state.
|
||||
* <br/>
|
||||
* <b>Note</b>: If the serializer is optional, values <i>must</i> be
|
||||
* wrapped in an {@link Optional}
|
||||
*
|
||||
* @param clazz Class to find serializer for
|
||||
* @param type Type to find serializer for
|
||||
* @param optional Optional state
|
||||
* @return The serializer, or null if none exists
|
||||
*/
|
||||
public static Serializer get(Class<?> clazz, boolean optional) {
|
||||
Validate.notNull(clazz, "Class cannot be null!");
|
||||
public static Serializer get(Type type, boolean optional) {
|
||||
Validate.notNull(type, "Type cannot be null!");
|
||||
initialize();
|
||||
|
||||
Serializer serializer = optional ? OPTIONAL_REGISTRY.get(clazz) : RAW_REGISTRY.get(clazz);
|
||||
Serializer serializer = optional ? OPTIONAL_REGISTRY.get(type) : RAW_REGISTRY.get(type);
|
||||
if (serializer == null) {
|
||||
throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + clazz + ">" : clazz));
|
||||
throw new IllegalArgumentException("No serializer found for " + (optional ? "Optional<" + type + ">" : type));
|
||||
}
|
||||
|
||||
return serializer;
|
||||
@@ -1067,52 +1110,41 @@ public class WrappedDataWatcher implements IDataWatcher {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Class<?>, Serializer> rawRegistry = new HashMap<>();
|
||||
Map<Class<?>, Serializer> optionalRegistry = new HashMap<>();
|
||||
Map<Type, Serializer> rawRegistry = new HashMap<>();
|
||||
Map<Type, Serializer> optionalRegistry = new HashMap<>();
|
||||
|
||||
List<Field> candidates = FuzzyReflection.fromClass(MinecraftReflection.getDataWatcherRegistryClass(), true)
|
||||
.getFieldListByType(MinecraftReflection.getDataWatcherSerializerClass());
|
||||
for (Field candidate : candidates) {
|
||||
Type generic = candidate.getGenericType();
|
||||
if (generic instanceof ParameterizedType) {
|
||||
ParameterizedType type = (ParameterizedType) generic;
|
||||
Type[] args = type.getActualTypeArguments();
|
||||
Type arg = args[0];
|
||||
|
||||
Class<?> innerClass;
|
||||
boolean optional = false;
|
||||
|
||||
if (arg instanceof Class<?>) {
|
||||
innerClass = (Class<?>) arg;
|
||||
} else if (arg instanceof ParameterizedType) {
|
||||
innerClass = (Class<?>) ((ParameterizedType) arg).getActualTypeArguments()[0];
|
||||
optional = true;
|
||||
} else {
|
||||
throw new IllegalStateException("Failed to find inner class of field " + candidate);
|
||||
}
|
||||
|
||||
if (candidate.getGenericType() instanceof ParameterizedType cpt) {
|
||||
Object serializer;
|
||||
|
||||
try {
|
||||
serializer = candidate.get(null);
|
||||
Objects.requireNonNull(serializer, "Failed to read serializer: " + candidate.getName());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException("Failed to read field " + candidate);
|
||||
}
|
||||
|
||||
if (serializer == null) {
|
||||
throw new RuntimeException("Failed to read serializer: " + candidate.getName());
|
||||
// all types are wrapped in EntityDataSerializer<?>
|
||||
Type serializingType = cpt.getActualTypeArguments()[0];
|
||||
if (serializingType instanceof ParameterizedType spt) {
|
||||
Class<?> root = (Class<?>) spt.getRawType();
|
||||
if (root == Optional.class) {
|
||||
// special case: EntityDataSerializer<Optional<?>>
|
||||
Type actualType = spt.getActualTypeArguments()[0];
|
||||
Serializer wrappedSerializer = new Serializer(actualType, serializer, true);
|
||||
optionalRegistry.put(actualType, wrappedSerializer);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (optional) {
|
||||
optionalRegistry.put(innerClass, new Serializer(innerClass, serializer, true));
|
||||
} else {
|
||||
rawRegistry.put(innerClass, new Serializer(innerClass, serializer, false));
|
||||
}
|
||||
Serializer wrappedSerializer = new Serializer(serializingType, serializer, false);
|
||||
rawRegistry.put(serializingType, wrappedSerializer);
|
||||
}
|
||||
}
|
||||
|
||||
RAW_REGISTRY = ImmutableMap.copyOf(rawRegistry);
|
||||
OPTIONAL_REGISTRY = ImmutableMap.copyOf(optionalRegistry);
|
||||
RAW_REGISTRY = Map.copyOf(rawRegistry);
|
||||
OPTIONAL_REGISTRY = Map.copyOf(optionalRegistry);
|
||||
}
|
||||
|
||||
// ---- Helper methods
|
||||
|
||||
@@ -35,18 +35,23 @@ public class WrappedGameProfile extends AbstractWrapper {
|
||||
GAME_PROFILE, String.class, String.class);
|
||||
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
|
||||
GAME_PROFILE, UUID.class, String.class);
|
||||
private static final ConstructorAccessor CREATE_UUID_STRING_PROPERTIES = Accessors.getConstructorAccessorOrNull(
|
||||
GAME_PROFILE, UUID.class, String.class, MinecraftReflection.getGameProfilePropertyMapClass());
|
||||
|
||||
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAccessorOrNull(
|
||||
GAME_PROFILE, "id", String.class);
|
||||
|
||||
private static final MethodAccessor GET_ID = Accessors.getMethodAccessorOrNull(
|
||||
GAME_PROFILE, "getId");
|
||||
private static final MethodAccessor GET_NAME = Accessors.getMethodAccessorOrNull(
|
||||
GAME_PROFILE, "getName");
|
||||
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessorOrNull(
|
||||
GAME_PROFILE, "getProperties");
|
||||
private static final MethodAccessor IS_COMPLETE = Accessors.getMethodAccessorOrNull(
|
||||
GAME_PROFILE, "isComplete");
|
||||
private static 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 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
|
||||
private static FieldAccessor PLAYER_PROFILE;
|
||||
@@ -117,6 +122,51 @@ public class WrappedGameProfile extends AbstractWrapper {
|
||||
this(parseUUID(id), name);
|
||||
}
|
||||
|
||||
private static Object createHandle(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
|
||||
if (CREATE_STRING_STRING != null) {
|
||||
return CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name);
|
||||
}
|
||||
|
||||
if (CREATE_UUID_STRING == null) {
|
||||
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
|
||||
}
|
||||
|
||||
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
|
||||
return CREATE_UUID_STRING.invoke(uuid, name);
|
||||
}
|
||||
|
||||
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
|
||||
// it's not allowed to pass null for both, so we need to pre-check that
|
||||
if (uuid == null && (name == null || name.isEmpty())) {
|
||||
throw new IllegalArgumentException("Name and ID cannot both be blank");
|
||||
}
|
||||
|
||||
// 1.21.9+ made PropertyMap's underlying map immutable, so we need to override it with a mutable map
|
||||
if (MinecraftVersion.v1_21_9.atOrAbove()) {
|
||||
return CREATE_UUID_STRING_PROPERTIES.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name,
|
||||
convertPropertyMap(properties));
|
||||
}
|
||||
|
||||
return CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name);
|
||||
}
|
||||
|
||||
private static Object convertPropertyMap(Multimap<String, WrappedSignedProperty> properties) {
|
||||
com.comphenix.protocol.wrappers.MutablePropertyMap map =
|
||||
new com.comphenix.protocol.wrappers.MutablePropertyMap();
|
||||
|
||||
if (properties == null || properties.isEmpty()) {
|
||||
return map;
|
||||
}
|
||||
|
||||
for (String key : properties.keySet()) {
|
||||
for (WrappedSignedProperty property : properties.get(key)) {
|
||||
map.put(key, (com.mojang.authlib.properties.Property) property.getHandle());
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new game profile with the given properties.
|
||||
* <p>
|
||||
@@ -127,24 +177,12 @@ public class WrappedGameProfile extends AbstractWrapper {
|
||||
*/
|
||||
public WrappedGameProfile(UUID uuid, String name) {
|
||||
super(GAME_PROFILE);
|
||||
setHandle(createHandle(uuid, name, null));
|
||||
}
|
||||
|
||||
if (CREATE_STRING_STRING != null) {
|
||||
setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name));
|
||||
} else if (CREATE_UUID_STRING != null) {
|
||||
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
|
||||
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
|
||||
// it's not allowed to pass null for both, so we need to pre-check that
|
||||
if (uuid == null && (name == null || name.isEmpty())) {
|
||||
throw new IllegalArgumentException("Name and ID cannot both be blank");
|
||||
}
|
||||
|
||||
setHandle(CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name));
|
||||
} else {
|
||||
setHandle(CREATE_UUID_STRING.invoke(uuid, name));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
|
||||
}
|
||||
public WrappedGameProfile(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
|
||||
super(GAME_PROFILE);
|
||||
setHandle(createHandle(uuid, name, properties));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,7 +195,12 @@ public class WrappedGameProfile extends AbstractWrapper {
|
||||
if (handle == null)
|
||||
return null;
|
||||
|
||||
return new WrappedGameProfile(handle);
|
||||
WrappedGameProfile delegate = new WrappedGameProfile(handle);
|
||||
if (MinecraftVersion.v1_21_9.atOrAbove()) {
|
||||
return new WrappedGameProfile(delegate.getUUID(), delegate.getName(), delegate.getProperties());
|
||||
} else {
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,7 +312,15 @@ public class WrappedGameProfile extends AbstractWrapper {
|
||||
|
||||
if (result == null) {
|
||||
Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle);
|
||||
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(GuavaWrappers.getBukkitMultimap(properties)) {
|
||||
|
||||
Multimap inner;
|
||||
if (MinecraftVersion.v1_21_9.atOrAbove()) {
|
||||
inner = properties;
|
||||
} else {
|
||||
inner = GuavaWrappers.getBukkitMultimap(properties);
|
||||
}
|
||||
|
||||
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(inner) {
|
||||
@Override
|
||||
protected Object toInner(WrappedSignedProperty outer) {
|
||||
return outer.handle;
|
||||
|
||||
@@ -3,8 +3,13 @@ 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;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
@@ -41,22 +46,39 @@ public final class WrappedLevelChunkData {
|
||||
|
||||
private static final FieldAccessor BLOCK_ENTITIES_DATA_ACCESSOR;
|
||||
private static final FieldAccessor HEIGHTMAPS_ACCESSOR;
|
||||
private static final EquivalentConverter<Map<EnumWrappers.HeightmapType, long[]>> HEIGHTMAPS_CONVERTER;
|
||||
private static final FieldAccessor BUFFER_ACCESSOR;
|
||||
|
||||
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()));
|
||||
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getNBTCompoundClass())
|
||||
.build()));
|
||||
if (MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(Map.class)
|
||||
.build()));
|
||||
HEIGHTMAPS_CONVERTER = BukkitConverters.getMapConverter(EnumWrappers.getHeightmapTypeConverter(), Converters.passthrough(long[].class));
|
||||
} else {
|
||||
HEIGHTMAPS_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getNBTCompoundClass())
|
||||
.build()));
|
||||
HEIGHTMAPS_CONVERTER = null;
|
||||
}
|
||||
BUFFER_ACCESSOR = Accessors.getFieldAccessor(reflection.getField(FuzzyFieldContract.newBuilder().typeExact(byte[].class).build()));
|
||||
}
|
||||
|
||||
@@ -68,22 +90,50 @@ public final class WrappedLevelChunkData {
|
||||
|
||||
/**
|
||||
* The heightmap of this chunk.
|
||||
* <p>
|
||||
* Removed in Minecraft 1.21.5.
|
||||
*
|
||||
* @return an NBT-Tag
|
||||
* @deprecated Use {@link ChunkData#getHeightmaps()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public NbtCompound getHeightmapsTag() {
|
||||
return NbtFactory.fromNMSCompound(HEIGHTMAPS_ACCESSOR.get(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heightmap tag of this chunk.
|
||||
* <p>
|
||||
* Removed in Minecraft 1.21.5.
|
||||
*
|
||||
* @param heightmapsTag the new heightmaps tag.
|
||||
* @deprecated Use {@link ChunkData#setHeightmaps(Map)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHeightmapsTag(NbtCompound heightmapsTag) {
|
||||
HEIGHTMAPS_ACCESSOR.set(handle, NbtFactory.fromBase(heightmapsTag).getHandle());
|
||||
}
|
||||
|
||||
/**
|
||||
* The heightmap of this chunk.
|
||||
*
|
||||
* @return a map containing the heightmaps
|
||||
*/
|
||||
public Map<EnumWrappers.HeightmapType, long[]> getHeightmaps() {
|
||||
return HEIGHTMAPS_CONVERTER.getSpecific(HEIGHTMAPS_ACCESSOR.get(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the heightmap tag of this chunk.
|
||||
* <p>
|
||||
* Removed in Minecraft 1.21.5.
|
||||
*
|
||||
* @param heightmaps the new heightmaps.
|
||||
*/
|
||||
public void setHeightmaps(Map<EnumWrappers.HeightmapType, long[]> heightmaps) {
|
||||
HEIGHTMAPS_ACCESSOR.set(handle, HEIGHTMAPS_CONVERTER.getGeneric(heightmaps));
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual structural data of this chunk as bytes.
|
||||
*
|
||||
@@ -130,14 +180,23 @@ public final class WrappedLevelChunkData {
|
||||
|
||||
/**
|
||||
* Creates a new wrapper using predefined values.
|
||||
* <p>
|
||||
* Removed in Minecraft 1.21.5.
|
||||
*
|
||||
* @param heightmapsTag the heightmaps tag
|
||||
* @param buffer the buffer
|
||||
* @param blockEntityInfo a list of wrapped block entities
|
||||
* @return a newly created wrapper
|
||||
* @deprecated Use {@link ChunkData#fromValues(Map, byte[], List)} instead.
|
||||
*/
|
||||
@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);
|
||||
@@ -145,6 +204,29 @@ public final class WrappedLevelChunkData {
|
||||
|
||||
return new ChunkData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new wrapper using predefined values.
|
||||
*
|
||||
* @param heightmaps the heightmaps
|
||||
* @param buffer the buffer
|
||||
* @param blockEntityInfo a list of wrapped block entities
|
||||
* @return a newly created wrapper
|
||||
*/
|
||||
public static ChunkData fromValues(Map<EnumWrappers.HeightmapType, long[]> heightmaps, byte[] buffer, List<BlockEntityInfo> blockEntityInfo) {
|
||||
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);
|
||||
data.setBlockEntityInfo(blockEntityInfo);
|
||||
|
||||
return new ChunkData(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,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) {
|
||||
@@ -472,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);
|
||||
}
|
||||
@@ -482,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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
@@ -183,59 +201,80 @@ public class WrappedParticle<T> {
|
||||
}
|
||||
|
||||
private static Object getRedstone(Object handle) {
|
||||
int r, g, b;
|
||||
float size;
|
||||
Color color;
|
||||
float scale;
|
||||
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
|
||||
if (MinecraftVersion.v1_21_2.atOrAbove()) {
|
||||
int rgb = (int) modifier.withType(int.class).read(0);
|
||||
color = Color.fromRGB(rgb);
|
||||
scale = (float) modifier.withType(float.class).read(0);
|
||||
} else if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
org.joml.Vector3f rgb = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(0);
|
||||
|
||||
r = (int) (rgb.x() * 255);
|
||||
g = (int) (rgb.y() * 255);
|
||||
b = (int) (rgb.z() * 255);
|
||||
size = (float) modifier.withType(float.class).read(0);
|
||||
int red = (int) (rgb.x() * 255);
|
||||
int green = (int) (rgb.y() * 255);
|
||||
int blue = (int) (rgb.z() * 255);
|
||||
|
||||
color = Color.fromRGB(red, green, blue);
|
||||
scale = (float) modifier.withType(float.class).read(0);
|
||||
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
if (VECTOR_3FA == null) {
|
||||
VECTOR_3FA = MinecraftReflection.getLibraryClass("com.mojang.math.Vector3fa");
|
||||
}
|
||||
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
|
||||
Object rgb = modifier.withType(VECTOR_3FA).read(0);
|
||||
StructureModifier<Object> rgbModifier = new StructureModifier<>(VECTOR_3FA).withTarget(rgb);
|
||||
|
||||
r = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
|
||||
g = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
|
||||
b = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
|
||||
size = (float) modifier.withType(float.class).read(0);
|
||||
int red = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
|
||||
int green = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
|
||||
int blue = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
|
||||
|
||||
color = Color.fromRGB(red, green, blue);
|
||||
scale = (float) modifier.withType(float.class).read(0);
|
||||
} else {
|
||||
StructureModifier<Float> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class);
|
||||
r = (int) (modifier.read(0) * 255);
|
||||
g = (int) (modifier.read(1) * 255);
|
||||
b = (int) (modifier.read(2) * 255);
|
||||
size = modifier.read(3);
|
||||
StructureModifier<Float> floatModifier = modifier.withType(float.class);
|
||||
|
||||
int red = (int) (floatModifier.read(0) * 255);
|
||||
int green = (int) (floatModifier.read(1) * 255);
|
||||
int blue = (int) (floatModifier.read(2) * 255);
|
||||
|
||||
color = Color.fromRGB(red, green, blue);
|
||||
scale = floatModifier.read(3);
|
||||
}
|
||||
|
||||
return new Particle.DustOptions(Color.fromRGB(r, g, b), size);
|
||||
return new Particle.DustOptions(color, scale);
|
||||
}
|
||||
|
||||
private static Object getDustTransition(Object handle) {
|
||||
int fromR, fromG, fromB, toR, toG, toB;
|
||||
float size;
|
||||
Color fromColor, toColor;
|
||||
float scale;
|
||||
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
if (MinecraftVersion.v1_21_2.atOrAbove()) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
int fromRgb = (int) modifier.withType(int.class).read(0);
|
||||
fromColor = Color.fromRGB(fromRgb);
|
||||
|
||||
int toRgb = (int) modifier.withType(int.class).read(1);
|
||||
toColor = Color.fromRGB(toRgb);
|
||||
|
||||
scale = (float) modifier.withType(float.class).read(0);
|
||||
} else if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
StructureModifier<Object> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle);
|
||||
org.joml.Vector3f toRGB = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(1);
|
||||
org.joml.Vector3f fromRGB = (org.joml.Vector3f) modifier.withType(org.joml.Vector3f.class).read(0);
|
||||
size = (float) modifier.withType(float.class).read(0);
|
||||
scale = (float) modifier.withType(float.class).read(0);
|
||||
|
||||
fromR = (int) (fromRGB.x() * 255);
|
||||
fromG = (int) (fromRGB.y() * 255);
|
||||
fromB = (int) (fromRGB.z() * 255);
|
||||
int fromR = (int) (fromRGB.x() * 255);
|
||||
int fromG = (int) (fromRGB.y() * 255);
|
||||
int fromB = (int) (fromRGB.z() * 255);
|
||||
fromColor = Color.fromRGB(fromR, fromG, fromB);
|
||||
|
||||
toR = (int) (toRGB.x() * 255);
|
||||
toG = (int) (toRGB.y() * 255);
|
||||
toB = (int) (toRGB.z() * 255);
|
||||
int toR = (int) (toRGB.x() * 255);
|
||||
int toG = (int) (toRGB.y() * 255);
|
||||
int toB = (int) (toRGB.z() * 255);
|
||||
toColor = Color.fromRGB(toR, toG, toB);
|
||||
} else if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
if (VECTOR_3FA == null) {
|
||||
VECTOR_3FA = MinecraftReflection.getLibraryClass("com.mojang.math.Vector3fa");
|
||||
@@ -245,37 +284,42 @@ public class WrappedParticle<T> {
|
||||
|
||||
Object toRGB = modifier.withType(VECTOR_3FA).read(0);
|
||||
Object fromRGB = modifier.withType(VECTOR_3FA).read(1);
|
||||
size = (float) modifier.withType(float.class).read(0);
|
||||
scale = (float) modifier.withType(float.class).read(0);
|
||||
StructureModifier<Object> rgbModifier = new StructureModifier<>(VECTOR_3FA).withTarget(fromRGB);
|
||||
StructureModifier<Object> rgbModifier2 = new StructureModifier<>(VECTOR_3FA).withTarget(toRGB);
|
||||
|
||||
fromR = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
|
||||
fromG = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
|
||||
fromB = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
|
||||
int fromR = (int) (rgbModifier.<Float>withType(float.class).read(0) * 255);
|
||||
int fromG = (int) (rgbModifier.<Float>withType(float.class).read(1) * 255);
|
||||
int fromB = (int) (rgbModifier.<Float>withType(float.class).read(2) * 255);
|
||||
fromColor = Color.fromRGB(fromR, fromG, fromB);
|
||||
|
||||
toR = (int) (rgbModifier2.<Float>withType(float.class).read(0) * 255);
|
||||
toG = (int) (rgbModifier2.<Float>withType(float.class).read(1) * 255);
|
||||
toB = (int) (rgbModifier2.<Float>withType(float.class).read(2) * 255);
|
||||
int toR = (int) (rgbModifier2.<Float>withType(float.class).read(0) * 255);
|
||||
int toG = (int) (rgbModifier2.<Float>withType(float.class).read(1) * 255);
|
||||
int toB = (int) (rgbModifier2.<Float>withType(float.class).read(2) * 255);
|
||||
toColor = Color.fromRGB(toR, toG, toB);
|
||||
} else {
|
||||
StructureModifier<Float> modifier = new StructureModifier<>(handle.getClass()).withTarget(handle).withType(float.class);
|
||||
toR = (int) (modifier.read(0) * 255);
|
||||
toG = (int) (modifier.read(1) * 255);
|
||||
toB = (int) (modifier.read(2) * 255);
|
||||
size = modifier.read(3);
|
||||
fromR = (int) (modifier.read(4) * 255);
|
||||
fromG = (int) (modifier.read(5) * 255);
|
||||
fromB = (int) (modifier.read(6) * 255);
|
||||
int toR = (int) (modifier.read(0) * 255);
|
||||
int toG = (int) (modifier.read(1) * 255);
|
||||
int toB = (int) (modifier.read(2) * 255);
|
||||
toColor = Color.fromRGB(toR, toG, toB);
|
||||
|
||||
scale = modifier.read(3);
|
||||
|
||||
int fromR = (int) (modifier.read(4) * 255);
|
||||
int fromG = (int) (modifier.read(5) * 255);
|
||||
int fromB = (int) (modifier.read(6) * 255);
|
||||
fromColor = Color.fromRGB(fromR, fromG, fromB);
|
||||
}
|
||||
|
||||
return new Particle.DustTransition(Color.fromRGB(fromR, fromG, fromB), Color.fromRGB(toR, toG, toB), size);
|
||||
return new Particle.DustTransition(fromColor, toColor, scale);
|
||||
}
|
||||
|
||||
public static <T> WrappedParticle<T> create(Particle particle, T data) {
|
||||
ensureMethods();
|
||||
|
||||
Object bukkitData = data;
|
||||
if (data instanceof WrappedBlockData) {
|
||||
WrappedBlockData blockData = (WrappedBlockData) data;
|
||||
if (data instanceof WrappedBlockData blockData) {
|
||||
bukkitData = toCraftData.invoke(null, blockData.getHandle());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
/**
|
||||
* Wrapper around the NMS {@code PositionMoveRotation} record, introduced in Minecraft 1.21.2.
|
||||
* <p>
|
||||
* This type is used in packets such as {@code ENTITY_TELEPORT} and {@code ENTITY_POSITION_SYNC}
|
||||
* to represent combined position, movement delta and rotation.
|
||||
* <p>
|
||||
* Example usage:
|
||||
* <pre>{@code
|
||||
* WrappedPositionMoveRotation posRot = WrappedPositionMoveRotation.create(
|
||||
* new Vector(x, y, z), // position
|
||||
* new Vector(0, 0, 0), // deltaMovement
|
||||
* yaw, // yRot
|
||||
* pitch // xRot
|
||||
* );
|
||||
* packet.getPositionMoveRotation().write(0, posRot);
|
||||
* }</pre>
|
||||
*/
|
||||
public class WrappedPositionMoveRotation extends AbstractWrapper {
|
||||
|
||||
private static Class<?> NMS_CLASS;
|
||||
private static ConstructorAccessor CONSTRUCTOR;
|
||||
private static StructureModifier<Object> BASE_MODIFIER;
|
||||
|
||||
private final StructureModifier<Object> modifier;
|
||||
|
||||
private WrappedPositionMoveRotation(Object handle) {
|
||||
super(getNmsClass());
|
||||
setHandle(handle);
|
||||
this.modifier = getBaseModifier().withTarget(handle);
|
||||
}
|
||||
|
||||
private static Class<?> getNmsClass() {
|
||||
if (NMS_CLASS == null) {
|
||||
NMS_CLASS = MinecraftReflection.getPositionMoveRotationClass();
|
||||
}
|
||||
return NMS_CLASS;
|
||||
}
|
||||
|
||||
private static StructureModifier<Object> getBaseModifier() {
|
||||
if (BASE_MODIFIER == null) {
|
||||
BASE_MODIFIER = new StructureModifier<>(getNmsClass());
|
||||
}
|
||||
return BASE_MODIFIER;
|
||||
}
|
||||
|
||||
private static ConstructorAccessor getConstructor() {
|
||||
if (CONSTRUCTOR == null) {
|
||||
CONSTRUCTOR = Accessors.getConstructorAccessor(
|
||||
getNmsClass(),
|
||||
MinecraftReflection.getVec3DClass(),
|
||||
MinecraftReflection.getVec3DClass(),
|
||||
float.class,
|
||||
float.class
|
||||
);
|
||||
}
|
||||
return CONSTRUCTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WrappedPositionMoveRotation} from the given NMS handle.
|
||||
*
|
||||
* @param handle - the NMS {@code PositionMoveRotation} instance.
|
||||
* @return the wrapper.
|
||||
*/
|
||||
public static WrappedPositionMoveRotation fromHandle(Object handle) {
|
||||
return new WrappedPositionMoveRotation(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code WrappedPositionMoveRotation} with the given values.
|
||||
*
|
||||
* @param position - the absolute position.
|
||||
* @param deltaMovement - the movement delta (velocity).
|
||||
* @param yRot - the yaw rotation.
|
||||
* @param xRot - the pitch rotation.
|
||||
* @return a new wrapper instance.
|
||||
*/
|
||||
public static WrappedPositionMoveRotation create(Vector position, Vector deltaMovement, float yRot, float xRot) {
|
||||
EquivalentConverter<Vector> conv = BukkitConverters.getVectorConverter();
|
||||
Object handle = getConstructor().invoke(
|
||||
conv.getGeneric(position),
|
||||
conv.getGeneric(deltaMovement),
|
||||
yRot,
|
||||
xRot
|
||||
);
|
||||
return new WrappedPositionMoveRotation(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the absolute position component.
|
||||
*
|
||||
* @return the position as a Bukkit {@link Vector}.
|
||||
*/
|
||||
public Vector getPosition() {
|
||||
return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the absolute position component.
|
||||
*
|
||||
* @param position - the new position.
|
||||
*/
|
||||
public void setPosition(Vector position) {
|
||||
modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(0, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the movement delta (velocity) component.
|
||||
*
|
||||
* @return the delta movement as a Bukkit {@link Vector}.
|
||||
*/
|
||||
public Vector getDeltaMovement() {
|
||||
return modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).read(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the movement delta (velocity) component.
|
||||
*
|
||||
* @param deltaMovement - the new delta movement.
|
||||
*/
|
||||
public void setDeltaMovement(Vector deltaMovement) {
|
||||
modifier.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter()).write(1, deltaMovement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the yaw rotation (y-axis rotation).
|
||||
*
|
||||
* @return the yaw as a float.
|
||||
*/
|
||||
public float getYRot() {
|
||||
StructureModifier<Float> floats = modifier.withType(float.class);
|
||||
return floats.read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the yaw rotation (y-axis rotation).
|
||||
*
|
||||
* @param yRot - the new yaw value.
|
||||
*/
|
||||
public void setYRot(float yRot) {
|
||||
modifier.withType(float.class).write(0, yRot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pitch rotation (x-axis rotation).
|
||||
*
|
||||
* @return the pitch as a float.
|
||||
*/
|
||||
public float getXRot() {
|
||||
StructureModifier<Float> floats = modifier.withType(float.class);
|
||||
return floats.read(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pitch rotation (x-axis rotation).
|
||||
*
|
||||
* @param xRot - the new pitch value.
|
||||
*/
|
||||
public void setXRot(float xRot) {
|
||||
modifier.withType(float.class).write(1, xRot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link EquivalentConverter} that converts between {@code WrappedPositionMoveRotation}
|
||||
* and the underlying NMS handle.
|
||||
*
|
||||
* @return the converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedPositionMoveRotation> getConverter() {
|
||||
return Converters.handle(AbstractWrapper::getHandle,
|
||||
WrappedPositionMoveRotation::fromHandle, WrappedPositionMoveRotation.class);
|
||||
}
|
||||
}
|
||||
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,11 +1,17 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.TeamCollisionRule;
|
||||
import com.comphenix.protocol.wrappers.EnumWrappers.TeamVisibility;
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A wrapper around the team parameters NMS class.
|
||||
@@ -35,11 +41,16 @@ public class WrappedTeamParameters extends AbstractWrapper {
|
||||
}
|
||||
|
||||
private final StructureModifier<Object> modifier;
|
||||
private final StructureModifier<TeamVisibility> visiblityModifier;
|
||||
private final StructureModifier<TeamCollisionRule> collisionRuleModifier;
|
||||
|
||||
public WrappedTeamParameters(Object handle) {
|
||||
super(getNmsClassOrThrow());
|
||||
setHandle(handle);
|
||||
|
||||
this.modifier = new StructureModifier<>(getNmsClassOrThrow()).withTarget(handle);
|
||||
this.visiblityModifier = this.modifier.withType(EnumWrappers.getTeamVisibilityClass(), EnumWrappers.getTeamVisibilityConverter());
|
||||
this.collisionRuleModifier = this.modifier.withType(EnumWrappers.getTeamCollisionRuleClass(), EnumWrappers.getTeamCollisionRuleConverter());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -58,22 +69,49 @@ public class WrappedTeamParameters extends AbstractWrapper {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getNametagVisibility() {
|
||||
return modifier.<String>withType(String.class).read(0);
|
||||
public TeamVisibility getNametagVisibility() {
|
||||
TeamVisibility visibility = this.visiblityModifier.readSafely(0);
|
||||
|
||||
if (visibility == null) {
|
||||
return TeamVisibility.fromName(modifier.<String>withType(String.class).read(0));
|
||||
} else {
|
||||
return visibility;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getCollisionRule() {
|
||||
return modifier.<String>withType(String.class).read(1);
|
||||
public TeamCollisionRule getCollisionRule() {
|
||||
TeamCollisionRule collisionRule = this.collisionRuleModifier.readSafely(0);
|
||||
|
||||
if (collisionRule == null) {
|
||||
return TeamCollisionRule.fromName(modifier.<String>withType(String.class).read(1));
|
||||
} else {
|
||||
return collisionRule;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public EnumWrappers.ChatFormatting getColor() {
|
||||
Object handle = modifier.withType(EnumWrappers.getChatFormattingClass()).read(0);
|
||||
return EnumWrappers.getChatFormattingConverter().getSpecific(handle);
|
||||
if (MinecraftVersion.v26_2.atOrAbove()) {
|
||||
Optional<?> optional = modifier.<Optional<?>>withType(Optional.class).read(0);
|
||||
Object teamColor = optional != null ? optional.orElse(null) : null;
|
||||
if (teamColor == null) {
|
||||
return EnumWrappers.ChatFormatting.RESET;
|
||||
}
|
||||
|
||||
return EnumWrappers.ChatFormatting.valueOf(((Enum<?>) teamColor).name());
|
||||
}
|
||||
|
||||
return modifier
|
||||
.withType(EnumWrappers.getChatFormattingClass(), EnumWrappers.getChatFormattingConverter())
|
||||
.read(0);
|
||||
}
|
||||
|
||||
public int getOptions() {
|
||||
if (MinecraftVersion.v26_2.atOrAbove()) {
|
||||
return (byte) modifier.withType(byte.class).read(0);
|
||||
}
|
||||
|
||||
return (int) modifier.withType(int.class).read(0);
|
||||
}
|
||||
|
||||
@@ -88,7 +126,8 @@ public class WrappedTeamParameters extends AbstractWrapper {
|
||||
|
||||
public static class Builder {
|
||||
private WrappedChatComponent displayName, prefix, suffix;
|
||||
private String nametagVisibility, collisionRule;
|
||||
private TeamVisibility nametagVisibility;
|
||||
private TeamCollisionRule collisionRule;
|
||||
private EnumWrappers.ChatFormatting color;
|
||||
private int options;
|
||||
|
||||
@@ -122,18 +161,36 @@ public class WrappedTeamParameters extends AbstractWrapper {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nametagVisibility(@NotNull String nametagVisibility) {
|
||||
public Builder nametagVisibility(@NotNull TeamVisibility nametagVisibility) {
|
||||
Preconditions.checkNotNull(nametagVisibility);
|
||||
this.nametagVisibility = nametagVisibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder collisionRule(@NotNull String collisionRule) {
|
||||
/**
|
||||
* Compatibility stub for FeatherBoard – accepts raw String visibility.
|
||||
*/
|
||||
public Builder nametagVisibility(@NotNull String visibility) {
|
||||
Preconditions.checkNotNull(visibility, "visibility string cannot be null");
|
||||
this.nametagVisibility = TeamVisibility.fromName(visibility);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder collisionRule(@NotNull TeamCollisionRule collisionRule) {
|
||||
Preconditions.checkNotNull(collisionRule);
|
||||
this.collisionRule = collisionRule;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility stub for FeatherBoard – accepts raw String collision rules.
|
||||
*/
|
||||
public Builder collisionRule(@NotNull String rule) {
|
||||
Preconditions.checkNotNull(rule, "collision rule cannot be null");
|
||||
this.collisionRule = TeamCollisionRule.fromName(rule);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder color(@NotNull EnumWrappers.ChatFormatting color) {
|
||||
Preconditions.checkNotNull(color);
|
||||
this.color = color;
|
||||
@@ -160,11 +217,36 @@ public class WrappedTeamParameters extends AbstractWrapper {
|
||||
wrapped.writeComponent(0, displayName);
|
||||
wrapped.writeComponent(1, prefix);
|
||||
wrapped.writeComponent(2, suffix);
|
||||
wrapped.modifier.withType(String.class).write(0, nametagVisibility);
|
||||
wrapped.modifier.withType(String.class).write(1, collisionRule);
|
||||
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
|
||||
wrapped.modifier.withType(int.class).write(0, options);
|
||||
|
||||
if (MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
wrapped.visiblityModifier.writeSafely(0, nametagVisibility);
|
||||
wrapped.collisionRuleModifier.writeSafely(0, collisionRule);
|
||||
} else {
|
||||
wrapped.modifier.withType(String.class).writeSafely(0, nametagVisibility.toString());
|
||||
wrapped.modifier.withType(String.class).writeSafely(1, collisionRule.toString());
|
||||
}
|
||||
|
||||
if (MinecraftVersion.v26_2.atOrAbove()) {
|
||||
wrapped.modifier.withType(Optional.class).write(0, Optional.ofNullable(toNmsTeamColor(color)));
|
||||
wrapped.modifier.withType(byte.class).write(0, (byte) options);
|
||||
} else {
|
||||
wrapped.modifier.withType(EnumWrappers.getChatFormattingClass()).write(0, EnumWrappers.getChatFormattingConverter().getGeneric(color));
|
||||
wrapped.modifier.withType(int.class).write(0, options);
|
||||
}
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static Object toNmsTeamColor(EnumWrappers.ChatFormatting color) {
|
||||
Class<?> teamColorClass = MinecraftReflection.getTeamColorClass()
|
||||
.orElseThrow(() -> new IllegalStateException("TeamColor class doesn't exist on this server version"));
|
||||
|
||||
try {
|
||||
return Enum.valueOf((Class) teamColorClass, color.name());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// formatting-only values (e.g. RESET) don't map to a team color
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
|
||||
String EMPTY_NAME = "";
|
||||
|
||||
/**
|
||||
* Get the type of each element.
|
||||
* Get the type of each element. For heterogeneous lists this will return {@link NbtType#TAG_COMPOUND TAG_COMPOUND}.
|
||||
* <p>
|
||||
* This will be {@link NbtType#TAG_END TAG_END} if the NBT list has just been created.
|
||||
* @return Element type.
|
||||
@@ -32,8 +32,12 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
|
||||
|
||||
/**
|
||||
* Set the type of each element.
|
||||
* <p>
|
||||
* In 1.21.5 and up lists can be heterogeneous, here the type is only used to convert elements added with {@link NbtList#addClosest(Object)}
|
||||
* @param type - type of each element.
|
||||
* @deprecated since 1.21.5 lists can be heterogeneous
|
||||
*/
|
||||
@Deprecated
|
||||
void setElementType(NbtType type);
|
||||
|
||||
/**
|
||||
@@ -41,9 +45,19 @@ public interface NbtList<TType> extends NbtBase<List<NbtBase<TType>>>, Iterable<
|
||||
* <p>
|
||||
* Note that the list must be typed by setting {@link #setElementType(NbtType)} before calling this function.
|
||||
* @param value - the value to add.
|
||||
* @deprecated since 1.21.5 lists can be heterogeneous. Use {@link NbtList#addClosest(Object, NbtType)}
|
||||
*/
|
||||
@Deprecated
|
||||
void addClosest(Object value);
|
||||
|
||||
|
||||
/**
|
||||
* Add a value to a typed list by attempting to convert it to the nearest value.
|
||||
* <p>
|
||||
* @param value - the value to add.
|
||||
* @param type - the resulting type of the entry
|
||||
*/
|
||||
void addClosest(Object value, NbtType type);
|
||||
|
||||
/**
|
||||
* Add a NBT list or NBT compound to the list.
|
||||
* @param element Element to add
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.block.BlockState;
|
||||
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
import com.comphenix.protocol.utility.MinecraftMethods;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftRegistryAccess;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.implementation.InvocationHandlerAdapter;
|
||||
|
||||
import net.bytebuddy.jar.asm.ClassReader;
|
||||
import net.bytebuddy.jar.asm.ClassVisitor;
|
||||
import net.bytebuddy.jar.asm.MethodVisitor;
|
||||
import net.bytebuddy.jar.asm.Opcodes;
|
||||
import net.bytebuddy.jar.asm.Type;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.block.BlockState;
|
||||
|
||||
/**
|
||||
* Manipulate tile entities.
|
||||
@@ -34,6 +32,10 @@ class TileEntityAccessor<T extends BlockState> {
|
||||
|
||||
private static final boolean BLOCK_DATA_INCL = MinecraftVersion.NETHER_UPDATE.atOrAbove()
|
||||
&& !MinecraftVersion.CAVES_CLIFFS_1.atOrAbove();
|
||||
private static final boolean USE_HOLDER_LOOKUP = MinecraftVersion.v1_21_5.atOrAbove();
|
||||
private static final boolean USE_TAG_VALUE_INPUT = MinecraftVersion.v1_21_6.atOrAbove();
|
||||
|
||||
private static final Class<?> TAG_VALUE_INPUT = MinecraftReflection.getNullableNMS("world.level.storage.TagValueInput");
|
||||
|
||||
/**
|
||||
* Token indicating that the given block state doesn't contain any tile entities.
|
||||
@@ -45,7 +47,7 @@ class TileEntityAccessor<T extends BlockState> {
|
||||
*/
|
||||
private static final Map<Class<?>, TileEntityAccessor<?>> cachedAccessors = new HashMap<>();
|
||||
|
||||
private static Constructor<?> nbtCompoundParserConstructor;
|
||||
private MethodAccessor createInputWrapper;
|
||||
|
||||
private FieldAccessor tileEntityField;
|
||||
private MethodAccessor readCompound;
|
||||
@@ -104,11 +106,50 @@ class TileEntityAccessor<T extends BlockState> {
|
||||
}
|
||||
|
||||
void findMethods(Class<?> type, T state) {
|
||||
if (BLOCK_DATA_INCL) {
|
||||
Class<?> tileEntityClass = MinecraftReflection.getTileEntityClass();
|
||||
Class<?> iBlockData = MinecraftReflection.getIBlockDataClass();
|
||||
Class<?> nbtCompound = MinecraftReflection.getNBTCompoundClass();
|
||||
Class<?> tileEntityClass = MinecraftReflection.getTileEntityClass();
|
||||
Class<?> nbtCompound = MinecraftReflection.getNBTCompoundClass();
|
||||
|
||||
if (USE_HOLDER_LOOKUP) {
|
||||
Class<?> holderLookup = MinecraftReflection.getHolderLookupProviderClass();
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(tileEntityClass, false);
|
||||
// should be equiv. to `CompoundTag saveWithFullMetadata(HolderLookup.Provider)`
|
||||
writeCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
|
||||
FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.returnTypeExact(nbtCompound)
|
||||
.parameterExactArray(holderLookup)
|
||||
.build()));
|
||||
|
||||
if (USE_TAG_VALUE_INPUT) {
|
||||
createInputWrapper = Accessors.getMethodAccessor(FuzzyReflection.fromClass(TAG_VALUE_INPUT).getMethod(
|
||||
FuzzyMethodContract.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.parameterExactArray(MinecraftReflection.getMinecraftClass("util.ProblemReporter"), holderLookup, nbtCompound)
|
||||
.build()));
|
||||
|
||||
// should be equiv. to `void loadWithComponents(ValueInput)`
|
||||
readCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
|
||||
FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.returnTypeVoid()
|
||||
.parameterSuperOf(TAG_VALUE_INPUT)
|
||||
.build()));
|
||||
} else {
|
||||
// should be equiv. to `void loadWithComponents(CompoundTag, HolderLookup.Provider)`
|
||||
readCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
|
||||
FuzzyMethodContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requireModifier(Modifier.FINAL)
|
||||
.returnTypeVoid()
|
||||
.parameterExactArray(nbtCompound, holderLookup)
|
||||
.build()));
|
||||
}
|
||||
} else if (BLOCK_DATA_INCL) {
|
||||
Class<?> iBlockData = MinecraftReflection.getIBlockDataClass();
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(tileEntityClass, false);
|
||||
writeCompound = Accessors.getMethodAccessor(fuzzy.getMethod(
|
||||
FuzzyMethodContract.newBuilder()
|
||||
@@ -214,12 +255,17 @@ class TileEntityAccessor<T extends BlockState> {
|
||||
* @return The compound.
|
||||
*/
|
||||
public NbtCompound readBlockState(T state) {
|
||||
NbtCompound output = NbtFactory.ofCompound("");
|
||||
Object tileEntity = tileEntityField.get(state);
|
||||
|
||||
// Write the block state to the output compound
|
||||
writeCompound.invoke(tileEntity, NbtFactory.fromBase(output).getHandle());
|
||||
return output;
|
||||
if (USE_HOLDER_LOOKUP) {
|
||||
Object tag = writeCompound.invoke(tileEntity, MinecraftRegistryAccess.get());
|
||||
return new WrappedCompound(tag);
|
||||
} else {
|
||||
NbtCompound output = NbtFactory.ofCompound("");
|
||||
writeCompound.invoke(tileEntity, NbtFactory.fromBase(output).getHandle());
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +278,14 @@ class TileEntityAccessor<T extends BlockState> {
|
||||
Object tileEntity = tileEntityField.get(state);
|
||||
|
||||
// Ensure the block state is set to the compound
|
||||
if (BLOCK_DATA_INCL) {
|
||||
if (USE_HOLDER_LOOKUP) {
|
||||
if (USE_TAG_VALUE_INPUT) {
|
||||
Object tagValueInput = createInputWrapper.invoke(null, null, MinecraftRegistryAccess.get(), NbtFactory.fromBase(compound).getHandle());
|
||||
readCompound.invoke(tileEntity, tagValueInput);
|
||||
} else {
|
||||
readCompound.invoke(tileEntity, NbtFactory.fromBase(compound).getHandle(), MinecraftRegistryAccess.get());
|
||||
}
|
||||
} else if (BLOCK_DATA_INCL) {
|
||||
Object blockData = BukkitUnwrapper.getInstance().unwrapItem(state);
|
||||
readCompound.invoke(tileEntity, blockData, NbtFactory.fromBase(compound).getHandle());
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.comphenix.protocol.wrappers.nbt;
|
||||
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.collection.ConvertedList;
|
||||
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
|
||||
import com.google.common.base.Joiner;
|
||||
@@ -103,7 +104,9 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
*/
|
||||
public WrappedList(Object handle) {
|
||||
this.container = new WrappedElement<>(handle);
|
||||
this.elementType = container.getSubType();
|
||||
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
this.elementType = container.getSubType();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +116,9 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
*/
|
||||
public WrappedList(Object handle, String name) {
|
||||
this.container = new WrappedElement<>(handle, name);
|
||||
this.elementType = container.getSubType();
|
||||
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
this.elementType = container.getSubType();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,13 +146,28 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
|
||||
@Override
|
||||
public NbtType getElementType() {
|
||||
return elementType;
|
||||
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
return elementType;
|
||||
} else {
|
||||
List<NbtBase<TType>> value = getValue();
|
||||
NbtType type = NbtType.TAG_END;
|
||||
for (NbtBase<TType> entry : value) {
|
||||
if (type == NbtType.TAG_END) {
|
||||
type = entry.getType();
|
||||
} else if (type != entry.getType()) {
|
||||
return NbtType.TAG_COMPOUND;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setElementType(NbtType type) {
|
||||
this.elementType = type;
|
||||
container.setSubType(type);
|
||||
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
container.setSubType(type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,14 +191,16 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
if (!element.getName().equals(EMPTY_NAME))
|
||||
throw new IllegalArgumentException("Cannot add a the named NBT tag " + element + " to a list.");
|
||||
|
||||
// Check element type
|
||||
if (getElementType() != NbtType.TAG_END) {
|
||||
if (!element.getType().equals(getElementType())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
|
||||
if (!MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
// Check element type
|
||||
if (getElementType() != NbtType.TAG_END) {
|
||||
if (!element.getType().equals(getElementType())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot add " + element + " of " + element.getType() + " to a list of type " + getElementType());
|
||||
}
|
||||
} else {
|
||||
container.setSubType(element.getType());
|
||||
}
|
||||
} else {
|
||||
container.setSubType(element.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,16 +257,21 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addClosest(Object value) {
|
||||
if (getElementType() == NbtType.TAG_END)
|
||||
if (elementType == NbtType.TAG_END)
|
||||
throw new IllegalStateException("This list has not been typed yet.");
|
||||
|
||||
|
||||
addClosest(value, elementType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addClosest(Object value, NbtType type) {
|
||||
if (value instanceof Number) {
|
||||
Number number = (Number) value;
|
||||
|
||||
// Convert the number
|
||||
switch (getElementType()) {
|
||||
switch (type) {
|
||||
case TAG_BYTE: add(number.byteValue()); break;
|
||||
case TAG_SHORT: add(number.shortValue()); break;
|
||||
case TAG_INT: add(number.intValue()); break;
|
||||
@@ -262,7 +289,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
|
||||
} else {
|
||||
// Just add it
|
||||
add((NbtBase<TType>) NbtFactory.ofWrapper(getElementType(), EMPTY_NAME, value));
|
||||
add((NbtBase<TType>) NbtFactory.ofWrapper(type, EMPTY_NAME, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +382,7 @@ class WrappedList<TType> implements NbtWrapper<List<NbtBase<TType>>>, NbtList<TT
|
||||
}
|
||||
|
||||
// Update the sub type as well
|
||||
if (lastElement != null) {
|
||||
if (lastElement != null && !MinecraftVersion.v1_21_5.atOrAbove()) {
|
||||
container.setSubType(lastElement.getType());
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -261,10 +261,9 @@ public class NbtConfigurationSerializer {
|
||||
|
||||
if (node instanceof List) {
|
||||
NbtList<Object> list = NbtFactory.ofList(decoded[0]);
|
||||
list.setElementType(type);
|
||||
|
||||
for (Object value : (List<Object>) node) {
|
||||
list.addClosest(toNodeValue(value, type));
|
||||
list.addClosest(toNodeValue(value, type), type);
|
||||
}
|
||||
|
||||
// Add the list
|
||||
|
||||
@@ -5,8 +5,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
|
||||
import java.util.Base64;
|
||||
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtBase;
|
||||
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
|
||||
@@ -23,7 +22,7 @@ public class NbtTextSerializer {
|
||||
* A default instance of this serializer.
|
||||
*/
|
||||
public static final NbtTextSerializer DEFAULT = new NbtTextSerializer();
|
||||
|
||||
|
||||
private NbtBinarySerializer binarySerializer;
|
||||
|
||||
public NbtTextSerializer() {
|
||||
@@ -59,7 +58,7 @@ public class NbtTextSerializer {
|
||||
binarySerializer.serialize(value, dataOutput);
|
||||
|
||||
// Serialize that array
|
||||
return Base64Coder.encodeLines(outputStream.toByteArray());
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +69,7 @@ public class NbtTextSerializer {
|
||||
* @throws IOException If we are unable to parse the input.
|
||||
*/
|
||||
public <TType> NbtWrapper<TType> deserialize(String input) throws IOException {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(input));
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(input));
|
||||
|
||||
return binarySerializer.deserialize(new DataInputStream(inputStream));
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.MinecraftGenerator;
|
||||
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
@@ -23,6 +25,7 @@ public final class ServerPingRecord implements ServerPingImpl {
|
||||
private static Class<?> SERVER_PING;
|
||||
private static Class<?> PLAYER_SAMPLE_CLASS;
|
||||
private static Class<?> SERVER_DATA_CLASS;
|
||||
private static Class<?> NAME_AND_ID_CLASS;
|
||||
|
||||
private static Class<?> GSON_CLASS;
|
||||
private static MethodAccessor GSON_TO_JSON;
|
||||
@@ -56,13 +59,37 @@ public final class ServerPingRecord implements ServerPingImpl {
|
||||
PLAYER_SAMPLE_CLASS = MinecraftReflection.getServerPingPlayerSampleClass();
|
||||
SERVER_DATA_CLASS = MinecraftReflection.getServerPingServerDataClass();
|
||||
|
||||
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING.getConstructors()[0]);
|
||||
PING_CTOR = Accessors.getConstructorAccessor(SERVER_PING, MinecraftReflection.getIChatBaseComponentClass(), Optional.class, Optional.class, Optional.class, boolean.class);
|
||||
|
||||
DATA_WRAPPER = AutoWrapper.wrap(ServerData.class, SERVER_DATA_CLASS);
|
||||
SAMPLE_WRAPPER = AutoWrapper.wrap(PlayerSample.class, PLAYER_SAMPLE_CLASS);
|
||||
FAVICON_WRAPPER = AutoWrapper.wrap(Favicon.class, MinecraftReflection.getMinecraftClass("network.protocol.status.ServerPing$a", "network.protocol.status.ServerStatus$Favicon"));
|
||||
|
||||
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
|
||||
if (MinecraftVersion.v1_21_10.atOrAbove()) {
|
||||
NAME_AND_ID_CLASS = MinecraftReflection.getMinecraftClass("server.players.NameAndId");
|
||||
NAME_AND_ID_WRAPPER = AutoWrapper.wrap(NameAndId.class, NAME_AND_ID_CLASS);
|
||||
|
||||
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(new EquivalentConverter<>() {
|
||||
@Override
|
||||
public Object getGeneric(WrappedGameProfile specific) {
|
||||
NameAndId wrapper = new NameAndId(specific.getUUID(), specific.getName());
|
||||
return NAME_AND_ID_WRAPPER.getGeneric(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedGameProfile getSpecific(Object generic) {
|
||||
NameAndId wrapper = NAME_AND_ID_WRAPPER.getSpecific(generic);
|
||||
return new WrappedGameProfile(wrapper.id, wrapper.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedGameProfile> getSpecificType() {
|
||||
return WrappedGameProfile.class;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
PROFILE_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedGameProfileConverter());
|
||||
}
|
||||
|
||||
DEFAULT_DESCRIPTION = WrappedChatComponent.fromLegacyText("A Minecraft Server");
|
||||
|
||||
@@ -124,11 +151,24 @@ public final class ServerPingRecord implements ServerPingImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class NameAndId {
|
||||
public UUID id;
|
||||
public String name;
|
||||
|
||||
public NameAndId(UUID id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public NameAndId() {
|
||||
this(MinecraftGenerator.SYS_UUID, "");
|
||||
}
|
||||
}
|
||||
|
||||
private static AutoWrapper<PlayerSample> SAMPLE_WRAPPER;
|
||||
|
||||
private static AutoWrapper<ServerData> DATA_WRAPPER;
|
||||
|
||||
private static AutoWrapper<Favicon> FAVICON_WRAPPER;
|
||||
private static AutoWrapper<NameAndId> NAME_AND_ID_WRAPPER;
|
||||
|
||||
private WrappedChatComponent description;
|
||||
private PlayerSample playerSample;
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package com.comphenix.integration.protocol;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.reflect.ExactReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Files;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -13,13 +21,6 @@ import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.PluginLoadOrder;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
package com.comphenix.integration.protocol;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.utility.StreamSerializer;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
@@ -15,6 +10,13 @@ import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.utility.StreamSerializer;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
public class SimpleMinecraftClient {
|
||||
|
||||
private static final int CONNECT_TIMEOUT = 2500;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.comphenix.integration.protocol;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class TestPingPacket {
|
||||
|
||||
@@ -3,60 +3,70 @@ package com.comphenix.protocol;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.utility.MinecraftReflectionTestUtil;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import io.papermc.paper.configuration.WorldConfiguration;
|
||||
import io.papermc.paper.registry.PaperRegistryAccess;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.commands.CommandDispatcher;
|
||||
import net.minecraft.core.IRegistry;
|
||||
import net.minecraft.core.IRegistryCustom;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.LayeredRegistryAccess;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import net.minecraft.resources.RegistryDataLoader;
|
||||
import net.minecraft.server.DataPackResources;
|
||||
import net.minecraft.server.DispenserRegistry;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.RegistryLayer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.ReloadableServerResources;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.packs.EnumResourcePackType;
|
||||
import net.minecraft.server.packs.repository.ResourcePackLoader;
|
||||
import net.minecraft.server.packs.repository.ResourcePackRepository;
|
||||
import net.minecraft.server.packs.repository.ResourcePackSourceVanilla;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.server.packs.repository.PackRepository;
|
||||
import net.minecraft.server.packs.repository.ServerPacksSource;
|
||||
import net.minecraft.server.packs.resources.MultiPackResourceManager;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.permissions.LevelBasedPermissionSet;
|
||||
import net.minecraft.server.permissions.PermissionSet;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.EntityTypes;
|
||||
import net.minecraft.tags.TagLoader;
|
||||
import net.minecraft.util.Util;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.flag.FeatureFlags;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.DataPackConfig;
|
||||
import net.minecraft.world.level.WorldDataConfiguration;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.material.FluidType;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Registry;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftLootTable;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemFactory;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftBlockTag;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftEntityTag;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftFluidTag;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftItemTag;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.Versioning;
|
||||
import org.bukkit.craftbukkit.CraftLootTable;
|
||||
import org.bukkit.craftbukkit.CraftRegistry;
|
||||
import org.bukkit.craftbukkit.CraftServer;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemFactory;
|
||||
import org.bukkit.craftbukkit.tag.CraftBlockTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftEntityTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftFluidTag;
|
||||
import org.bukkit.craftbukkit.tag.CraftItemTag;
|
||||
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
||||
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||
import org.bukkit.craftbukkit.util.Versioning;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
|
||||
@@ -75,6 +85,15 @@ public class BukkitInitialization {
|
||||
private boolean initialized;
|
||||
private boolean packaged;
|
||||
|
||||
private static ServerLevel mockServerLevel;
|
||||
|
||||
/**
|
||||
* @return the mocked NMS server level set up during initialization, usable as a Level for constructing test entities
|
||||
*/
|
||||
public static ServerLevel getMockServerLevel() {
|
||||
return mockServerLevel;
|
||||
}
|
||||
|
||||
private BukkitInitialization() {
|
||||
System.out.println("Created new BukkitInitialization on " + Thread.currentThread().getName());
|
||||
}
|
||||
@@ -111,36 +130,89 @@ public class BukkitInitialization {
|
||||
instance.setPackage();
|
||||
|
||||
// Minecraft Data Init
|
||||
SharedConstants.a(); // .tryDetectVersion()
|
||||
DispenserRegistry.a(); // .bootStrap()
|
||||
SharedConstants.tryDetectVersion(); // .tryDetectVersion()
|
||||
Bootstrap.bootStrap(); // .bootStrap()
|
||||
Bootstrap.validate();
|
||||
|
||||
ResourcePackRepository resourcePackRepository = ResourcePackSourceVanilla.c(); // .createVanillaTrustedRepository()
|
||||
resourcePackRepository.a(); // .reload()
|
||||
PackRepository repo = ServerPacksSource.createVanillaTrustedRepository();
|
||||
MinecraftServer.configurePackRepository(
|
||||
repo,
|
||||
new WorldDataConfiguration(
|
||||
new DataPackConfig(
|
||||
FeatureFlags.REGISTRY.toNames(FeatureFlags.REGISTRY.allFlags())
|
||||
.stream()
|
||||
.map(Identifier::getPath)
|
||||
.toList(),
|
||||
List.of()),
|
||||
FeatureFlags.REGISTRY.allFlags()),
|
||||
true,
|
||||
false);
|
||||
|
||||
ResourceManager resourceManager = new ResourceManager(
|
||||
EnumResourcePackType.b /* SERVER_DATA */,
|
||||
resourcePackRepository.c() /* getAvailablePacks() */ .stream().map(ResourcePackLoader::f /* openFull() */).collect(Collectors.toList()));
|
||||
LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = RegistryLayer.a(); // .createRegistryAccess()
|
||||
layeredRegistryAccess = WorldLoader.b(resourceManager, layeredRegistryAccess, RegistryLayer.b /* WORLDGEN */, RegistryDataLoader.a /* WORLDGEN_REGISTRIES */); // .loadAndReplaceLayer()
|
||||
IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().d(); // .compositeAccess().freeze()
|
||||
// IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().c(); // .compositeAccess().freeze()
|
||||
ResourceManager resourceManager = new MultiPackResourceManager(
|
||||
PackType.SERVER_DATA,
|
||||
repo.openAllSelected()
|
||||
);
|
||||
|
||||
DataPackResources dataPackResources = DataPackResources.a(
|
||||
resourceManager,
|
||||
layeredRegistryAccess,
|
||||
FeatureFlags.d.a() /* REGISTRY.allFlags() */,
|
||||
CommandDispatcher.ServerType.b /* DEDICATED */,
|
||||
0,
|
||||
MoreExecutors.directExecutor(),
|
||||
MoreExecutors.directExecutor()
|
||||
).join();
|
||||
dataPackResources.g();
|
||||
LayeredRegistryAccess<RegistryLayer> layeredAccess1 = RegistryLayer.createRegistryAccess();
|
||||
List<net.minecraft.core.Registry.PendingTags<?>> tags = TagLoader.loadTagsForExistingRegistries(resourceManager, layeredAccess1.getLayer(RegistryLayer.STATIC));
|
||||
RegistryAccess.Frozen access1 = layeredAccess1.getAccessForLoading(RegistryLayer.WORLD);
|
||||
List<HolderLookup.RegistryLookup<?>> list1 = TagLoader.buildUpdatedLookups(access1, tags);
|
||||
|
||||
RegistryAccess.Frozen access2;
|
||||
|
||||
try {
|
||||
access2 = RegistryDataLoader.load(
|
||||
resourceManager,
|
||||
list1,
|
||||
RegistryDataLoader.WORLD_REGISTRIES,
|
||||
Util.backgroundExecutor()).get();
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
LayeredRegistryAccess<RegistryLayer> layeredAccess2 =
|
||||
layeredAccess1.replaceFrom(RegistryLayer.WORLD, access2);
|
||||
List<HolderLookup.RegistryLookup<?>> dimensionLookups =
|
||||
Stream.concat(list1.stream(), access2.listRegistries()).toList();
|
||||
RegistryAccess.Frozen dimensions = RegistryDataLoader.load(
|
||||
resourceManager,
|
||||
dimensionLookups,
|
||||
RegistryDataLoader.DIMENSION_REGISTRIES,
|
||||
Util.backgroundExecutor()).join();
|
||||
layeredAccess2 = layeredAccess2.replaceFrom(RegistryLayer.DIMENSIONS, dimensions);
|
||||
|
||||
try {
|
||||
Class.forName(Registry.class.getName());
|
||||
} catch (ClassNotFoundException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
|
||||
RegistryAccess.Frozen registryCustom = layeredAccess2.compositeAccess().freeze();
|
||||
PaperRegistryAccess paperRegistryAccess = PaperRegistryAccess.instance();
|
||||
GlobalConfiguration globalConfiguration = new GlobalConfiguration();
|
||||
globalConfiguration.collisions = globalConfiguration.new Collisions();
|
||||
Accessors.getMethodAccessor(
|
||||
GlobalConfiguration.class,
|
||||
"set",
|
||||
GlobalConfiguration.class).invoke(null, globalConfiguration);
|
||||
|
||||
ReloadableServerResources dataPackResources = ReloadableServerResources.loadResources(
|
||||
resourceManager,
|
||||
layeredAccess2,
|
||||
tags,
|
||||
FeatureFlags.REGISTRY.allFlags() /* REGISTRY.allFlags() */,
|
||||
Commands.CommandSelection.DEDICATED /* DEDICATED */,
|
||||
LevelBasedPermissionSet.ALL,
|
||||
MoreExecutors.directExecutor(),
|
||||
MoreExecutors.directExecutor()
|
||||
).join();
|
||||
dataPackResources.updateComponentsAndStaticRegistryTags();
|
||||
|
||||
/* try {
|
||||
IRegistry.class.getName();
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} */
|
||||
|
||||
String releaseTarget = MinecraftReflectionTestUtil.RELEASE_TARGET;
|
||||
String serverVersion = CraftServer.class.getPackage().getImplementationVersion();
|
||||
@@ -149,7 +221,14 @@ public class BukkitInitialization {
|
||||
CraftServer mockedServer = mock(CraftServer.class);
|
||||
DedicatedServer mockedGameServer = mock(DedicatedServer.class);
|
||||
|
||||
when(mockedGameServer.bc()/*registryAccess*/).thenReturn(registryCustom);
|
||||
when(mockedGameServer.registryAccess()).thenReturn(registryCustom);
|
||||
try {
|
||||
FieldAccessor serverInstance = Accessors.getFieldAccessor(
|
||||
MinecraftServer.class.getDeclaredField("SERVER"));
|
||||
serverInstance.set(null, mockedGameServer);
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
|
||||
when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft"));
|
||||
when(mockedServer.getName()).thenReturn("Mock Server");
|
||||
@@ -162,11 +241,11 @@ public class BukkitInitialization {
|
||||
when(mockedServer.getUnsafe()).thenReturn(CraftMagicNumbers.INSTANCE);
|
||||
when(mockedServer.getLootTable(any())).thenAnswer(invocation -> {
|
||||
NamespacedKey key = invocation.getArgument(0);
|
||||
return new CraftLootTable(key, dataPackResources.b().b(CraftLootTable.bukkitKeyToMinecraft(key)));
|
||||
return new CraftLootTable(key, dataPackResources.fullRegistries().getLootTable(CraftLootTable.bukkitKeyToMinecraft(key)));
|
||||
});
|
||||
when(mockedServer.getRegistry(any())).thenAnswer(invocation -> {
|
||||
Class<Keyed> registryType = invocation.getArgument(0);
|
||||
Object registry = CraftRegistry.createRegistry(registryType, registryCustom);
|
||||
Object registry = paperRegistryAccess.getRegistry(registryType);
|
||||
|
||||
if (registry == null) {
|
||||
System.err.println("WARN: Missing registry for " + registryType);
|
||||
@@ -179,35 +258,35 @@ public class BukkitInitialization {
|
||||
when(mockedServer.getTag(any(), any(), any())).then(mock -> {
|
||||
String registry = mock.getArgument(0);
|
||||
Class<?> clazz = mock.getArgument(2);
|
||||
MinecraftKey key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
|
||||
Identifier key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
|
||||
|
||||
switch (registry) {
|
||||
case org.bukkit.Tag.REGISTRY_BLOCKS -> {
|
||||
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have block type");
|
||||
TagKey<Block> blockTagKey = TagKey.a(Registries.f, key);
|
||||
if (BuiltInRegistries.e.b(blockTagKey).isPresent()) {
|
||||
return new CraftBlockTag(BuiltInRegistries.e, blockTagKey);
|
||||
TagKey<Block> blockTagKey = TagKey.create(Registries.BLOCK, key);
|
||||
if (BuiltInRegistries.BLOCK.get(blockTagKey).isPresent()) {
|
||||
return new CraftBlockTag(BuiltInRegistries.BLOCK, blockTagKey);
|
||||
}
|
||||
}
|
||||
case org.bukkit.Tag.REGISTRY_ITEMS -> {
|
||||
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have item type");
|
||||
TagKey<Item> itemTagKey = TagKey.a(Registries.K, key);
|
||||
if (BuiltInRegistries.g.b(itemTagKey).isPresent()) {
|
||||
return new CraftItemTag(BuiltInRegistries.g, itemTagKey);
|
||||
TagKey<Item> itemTagKey = TagKey.create(Registries.ITEM, key);
|
||||
if (BuiltInRegistries.ITEM.get(itemTagKey).isPresent()) {
|
||||
return new CraftItemTag(BuiltInRegistries.ITEM, itemTagKey);
|
||||
}
|
||||
}
|
||||
case org.bukkit.Tag.REGISTRY_FLUIDS -> {
|
||||
Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type");
|
||||
TagKey<FluidType> fluidTagKey = TagKey.a(Registries.D, key);
|
||||
if (BuiltInRegistries.c.b(fluidTagKey).isPresent()) {
|
||||
return new CraftFluidTag(BuiltInRegistries.c, fluidTagKey);
|
||||
TagKey<Fluid> fluidTagKey = TagKey.create(Registries.FLUID, key);
|
||||
if (BuiltInRegistries.FLUID.get(fluidTagKey).isPresent()) {
|
||||
return new CraftFluidTag(BuiltInRegistries.FLUID, fluidTagKey);
|
||||
}
|
||||
}
|
||||
case org.bukkit.Tag.REGISTRY_ENTITY_TYPES -> {
|
||||
Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type");
|
||||
TagKey<EntityTypes<?>> entityTagKey = TagKey.a(Registries.z, key);
|
||||
if (BuiltInRegistries.f.b(entityTagKey).isPresent()) {
|
||||
return new CraftEntityTag(BuiltInRegistries.f, entityTagKey);
|
||||
TagKey<EntityType<?>> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key);
|
||||
if (BuiltInRegistries.ENTITY_TYPE.get(entityTagKey).isPresent()) {
|
||||
return new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey);
|
||||
}
|
||||
}
|
||||
default -> throw new IllegalArgumentException();
|
||||
@@ -216,8 +295,20 @@ public class BukkitInitialization {
|
||||
return null;
|
||||
});
|
||||
|
||||
WorldServer nmsWorld = mock(WorldServer.class);
|
||||
ServerLevel nmsWorld = mock(ServerLevel.class);
|
||||
SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class);
|
||||
ConstructorAccessor worldConfigurationConstructor = Accessors.getConstructorAccessor(
|
||||
WorldConfiguration.class,
|
||||
SpigotWorldConfig.class,
|
||||
Identifier.class);
|
||||
WorldConfiguration worldConfiguration = (WorldConfiguration) worldConfigurationConstructor.invoke(
|
||||
mockWorldConfig,
|
||||
Identifier.parse("minecraft:test"));
|
||||
worldConfiguration.entities = worldConfiguration.new Entities();
|
||||
worldConfiguration.entities.spawning =
|
||||
worldConfiguration.entities.new Spawning();
|
||||
worldConfiguration.entities.spawning.despawnTime = new Reference2ObjectOpenHashMap<>();
|
||||
when(nmsWorld.paperConfig()).thenReturn(worldConfiguration);
|
||||
|
||||
try {
|
||||
FieldAccessor spigotConfig = Accessors.getFieldAccessor(nmsWorld.getClass().getField("spigotConfig"));
|
||||
@@ -228,6 +319,7 @@ public class BukkitInitialization {
|
||||
|
||||
CraftWorld world = mock(CraftWorld.class);
|
||||
when(world.getHandle()).thenReturn(nmsWorld);
|
||||
mockServerLevel = nmsWorld;
|
||||
|
||||
List<World> worlds = Collections.singletonList(world);
|
||||
when(mockedServer.getWorlds()).thenReturn(worlds);
|
||||
@@ -237,8 +329,7 @@ public class BukkitInitialization {
|
||||
CraftRegistry.setMinecraftRegistry(registryCustom);
|
||||
|
||||
// Init Enchantments
|
||||
Enchantments.A.getClass();
|
||||
// Enchantment.stopAcceptingRegistrations();
|
||||
Enchantments.AQUA_AFFINITY.getClass();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
@@ -284,5 +375,35 @@ public class BukkitInitialization {
|
||||
List<T> empty = Collections.emptyList();
|
||||
return empty.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<NamespacedKey> keyStream() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NamespacedKey getKey(T value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTag(io.papermc.paper.registry.tag.TagKey<T> key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public io.papermc.paper.registry.tag.Tag<T> getTag(io.papermc.paper.registry.tag.TagKey<T> key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.Collection<io.papermc.paper.registry.tag.Tag<T>> getTags() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,13 @@ package com.comphenix.protocol;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -28,15 +31,14 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Play.Server;
|
||||
import com.comphenix.protocol.PacketType.Protocol;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
||||
|
||||
import net.minecraft.network.protocol.login.PacketLoginInStart;
|
||||
import net.minecraft.network.protocol.common.ClientboundPostEffectsPacket;
|
||||
import net.minecraft.network.protocol.common.ClientboundStoreCookiePacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
|
||||
|
||||
/**
|
||||
* @author dmulloy2
|
||||
@@ -62,7 +64,6 @@ public class PacketTypeTest {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// public void generateNewPackets() throws Exception {
|
||||
BukkitInitialization.initializeAll();
|
||||
|
||||
PacketType.onDynamicCreate = (type, className) -> {
|
||||
@@ -257,10 +258,41 @@ public class PacketTypeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinecraft263PacketMappings() {
|
||||
assertEquals(0x25, PacketType.Play.Server.ADD_TRANSIENT_BLOCK.getCurrentId());
|
||||
assertEquals(0x26, PacketType.Play.Server.UNLOAD_CHUNK.getCurrentId());
|
||||
assertNotEquals(PacketType.Play.Server.ADD_TRANSIENT_BLOCK, PacketType.Play.Server.UNLOAD_CHUNK);
|
||||
|
||||
assertEquals(0x53, PacketType.Play.Server.POST_EFFECTS.getCurrentId());
|
||||
assertEquals(0x7B, PacketType.Play.Server.SWING_ANIMATION.getCurrentId());
|
||||
assertEquals(0x2E, PacketType.Play.Client.PUNCH.getCurrentId());
|
||||
assertNotEquals(PacketType.Play.Client.PUNCH, PacketType.Play.Client.RECIPE_SETTINGS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinecraft263ConfigurationPacketMappings() {
|
||||
assertEquals(0x0A, PacketType.Configuration.Server.POST_EFFECTS.getCurrentId());
|
||||
assertEquals(ClientboundPostEffectsPacket.class, PacketType.Configuration.Server.POST_EFFECTS.getPacketClass());
|
||||
assertEquals(0x0B, PacketType.Configuration.Server.STORE_COOKIE.getCurrentId());
|
||||
assertEquals(ClientboundStoreCookiePacket.class, PacketType.Configuration.Server.STORE_COOKIE.getPacketClass());
|
||||
assertEquals(0x14, PacketType.Configuration.Server.CODE_OF_CONDUCT.getCurrentId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPacketCodec() {
|
||||
Set<PacketType> notSerializable = Set.of(PacketType.Play.Server.BUNDLE);
|
||||
for (PacketType type : PacketType.values()) {
|
||||
if (!notSerializable.contains(type) && type.isSupported()) {
|
||||
assertNotNull(PacketRegistry.getStreamCodec(type.getPacketClass()), "missing serializer for " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginStart() {
|
||||
// This packet is critical for handleLoin
|
||||
assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass());
|
||||
assertEquals(ServerboundHelloPacket.class, PacketType.Login.Client.START.getPacketClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user