Merge pull request #24 from steveb05/feat/spigot-entityid-provider
Implement spigot entity id provider
This commit is contained in:
commit
7df63952c7
2 changed files with 163 additions and 0 deletions
|
@ -0,0 +1,162 @@
|
|||
package me.tofaa.entitylib.spigot;
|
||||
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.entity.type.EntityType;
|
||||
import me.tofaa.entitylib.EntityIdProvider;
|
||||
import me.tofaa.entitylib.Platform;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.UnsafeValues;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Internal {@link EntityIdProvider} for Spigot servers, handling version and platform differences.
|
||||
*/
|
||||
public final class SpigotEntityIdProvider implements EntityIdProvider {
|
||||
|
||||
/** The platform abstraction. */
|
||||
private final Platform<JavaPlugin> platform;
|
||||
/** The supplier for entity IDs, dynamically chosen by {@link #detectIdSupplier()}. */
|
||||
private final Supplier<Integer> entityIdSupplier ;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SpigotEntityIdProvider}.
|
||||
*
|
||||
* @param platform The platform instance. Must not be null.
|
||||
*/
|
||||
public SpigotEntityIdProvider(final @NotNull Platform<JavaPlugin> platform) {
|
||||
this.platform = platform;
|
||||
this.entityIdSupplier = detectIdSupplier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int provide(@NotNull UUID entityUUID, @NotNull EntityType entityType) {
|
||||
// UUID and EntityType are unused in Minecraft id generation.
|
||||
return entityIdSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects and configures the appropriate {@link Supplier} for entity IDs based on server version
|
||||
* and platform.
|
||||
* Logic:
|
||||
* <ul>
|
||||
* <li>Paper (1.16+): Use `{@link UnsafeValues#nextEntityId()}`. (Paper API)</li>
|
||||
* <li>1.14+: Access `AtomicInteger` field ("entityCount", "d", "c").</li>
|
||||
* <li>1.8+: Access legacy `int` field "entityCount".</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return A {@link Supplier} instance that provides the next entity ID.
|
||||
* @throws IllegalStateException if the entity counter field cannot be located or accessed.
|
||||
*/
|
||||
private Supplier<Integer> detectIdSupplier() {
|
||||
final ServerVersion serverVersion = platform.getAPI().getPacketEvents().getServerManager().getVersion();
|
||||
|
||||
if (isPaper() && serverVersion.isNewerThanOrEquals(ServerVersion.V_1_16)) {
|
||||
return Bukkit.getUnsafe()::nextEntityId; // Paper API
|
||||
}
|
||||
|
||||
final Class<?> entityClass = getEntityClass();
|
||||
if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_14)) {
|
||||
final Field entityAtomicField = getField(entityClass, "entityCount", "d", "c"); // Obfuscated names
|
||||
if (entityAtomicField == null) {
|
||||
throw new IllegalStateException("Could not find entity counter field");
|
||||
}
|
||||
try {
|
||||
entityAtomicField.setAccessible(true);
|
||||
final AtomicInteger counter = (AtomicInteger) entityAtomicField.get(null);
|
||||
return counter::incrementAndGet;
|
||||
} catch (final Exception exception) {
|
||||
throw new IllegalStateException("Failed to access entity counter", exception);
|
||||
}
|
||||
}
|
||||
|
||||
final Field entityLegacyField = getField(entityClass, "entityCount");
|
||||
if (entityLegacyField == null) {
|
||||
throw new IllegalStateException("Could not find legacy entity counter field");
|
||||
}
|
||||
try {
|
||||
entityLegacyField.setAccessible(true);
|
||||
return () -> {
|
||||
try {
|
||||
final int entityId = entityLegacyField.getInt(null);
|
||||
entityLegacyField.setInt(null, entityId + 1);
|
||||
return entityId;
|
||||
} catch (final Exception exception) {
|
||||
throw new IllegalStateException("Failed to modify entity counter", exception);
|
||||
}
|
||||
};
|
||||
} catch (final Exception exception) {
|
||||
throw new IllegalStateException("Failed to access legacy entity counter", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves server's internal `Entity` class, handling version-specific packages.
|
||||
* In Minecraft versions 1.17 and later, the `Entity` class resides in `net.minecraft.world.entity.Entity`.
|
||||
* Prior to 1.17, it is located in `net.minecraft.server.[version].Entity`.
|
||||
*
|
||||
* @return Resolved `Entity` class.
|
||||
* @throws IllegalStateException if the `Entity` class cannot be found.
|
||||
*/
|
||||
private Class<?> getEntityClass() {
|
||||
final ServerVersion serverVersion = platform.getAPI().getPacketEvents().getServerManager().getVersion();
|
||||
final boolean isFlattened = serverVersion.isNewerThanOrEquals(ServerVersion.V_1_17);
|
||||
|
||||
final String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
final String packagePath = isFlattened ? "net.minecraft.world.entity" : "net.minecraft.server." + version;
|
||||
|
||||
try {
|
||||
return Class.forName(packagePath + ".Entity");
|
||||
} catch (final ClassNotFoundException exception) {
|
||||
throw new IllegalStateException("Could not find Entity class", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to retrieve a declared field from a class by trying multiple names.
|
||||
* @param clazz The class to examine for the field.
|
||||
* @param possibleNames An array of potential field names to attempt retrieval with.
|
||||
* @return The {@link Field} if a matching field is found, otherwise {@code null}.
|
||||
*/
|
||||
private static Field getField(final Class<?> clazz, final String... possibleNames) {
|
||||
for (final String name : possibleNames) {
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (final NoSuchFieldException ignored) {
|
||||
// Field name not found, proceed to the next
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the server environment is Paper by checking for Paper-specific classes.
|
||||
* @return true if Paper is detected.
|
||||
*/
|
||||
private static boolean isPaper() {
|
||||
return Stream.of(
|
||||
"com.destroystokyo.paper.PaperConfig",
|
||||
"io.papermc.paper.configuration.Configuration"
|
||||
).anyMatch(SpigotEntityIdProvider::hasClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to check if a class exists on the classpath.
|
||||
* @param className Class name to check.
|
||||
* @return true if class exists.
|
||||
*/
|
||||
private static boolean hasClass(final String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (final ClassNotFoundException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ public class SpigotEntityLibPlatform extends AbstractPlatform<JavaPlugin> {
|
|||
super.setupApi(settings);
|
||||
this.logger = settings.shouldUsePlatformLogger() ? handle.getLogger() : Logger.getLogger("EntityLib");
|
||||
this.api = new SpigotEntityLibAPI(this, settings);
|
||||
this.setEntityIdProvider(new SpigotEntityIdProvider(this));
|
||||
this.api.onLoad();
|
||||
this.api.onEnable();
|
||||
if (settings.shouldUseBstats()) {
|
||||
|
|
Loading…
Reference in a new issue