diff --git a/pom.xml b/pom.xml
index e4267cba..213ee0c7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
+ * 26.2 以降は {@code net.minecraft.world.entity.monster.cubemob} パッケージに移動されました。 + */ + private static Class> getMagmaCubeClass() { + try { + return WORLD_ENTITY_MONSTER.getClass("MagmaCube"); + } catch (IllegalArgumentException e) { + return WORLD_ENTITY_MONSTER_CUBEMOB.getClass("MagmaCube"); + } + } + PaperRemappedAccessorLegacy() throws ReflectiveOperationException { // sendPacket field(SERVER_LEVEL.getClass("ServerPlayer")) @@ -257,13 +271,13 @@ public class PaperRemappedAccessorLegacy implements NativeAccessor { .findFirst("ContainerMenu.checkReachable"); // newMagmaCube - construct(WORLD_ENTITY_MONSTER.getClass("MagmaCube")) + construct(getMagmaCubeClass()) .parameterTypes(WORLD_ENTITY.getClass("EntityType"), WORLD_LEVEL.getClass("Level")) .findFirst("MagmaCube"); // newCraftMagmaCube construct(ENTITY.getClass("CraftMagmaCube")) - .parameterTypes(CRAFTBUKKIT.getClass("CraftServer"), WORLD_ENTITY_MONSTER.getClass("MagmaCube")) + .parameterTypes(CRAFTBUKKIT.getClass("CraftServer"), getMagmaCubeClass()) .findFirst("CraftMagmaCube"); // newClientboundSetEntityDataPacket @@ -315,10 +329,50 @@ public class PaperRemappedAccessorLegacy implements NativeAccessor { this.m = methods(); this.c = constructs(); this.blockPosZero = c.newInstance("BlockPos", INT_ARRAY_ZERO_3); - this.magmaCubeType = field(WORLD_ENTITY.getClass("EntityType")).modifiers(STATIC).name("MAGMA_CUBE").fieldType(WORLD_ENTITY.getClass("EntityType")).findFirst().get(null); + this.magmaCubeType = getMagmaCubeType(); this.unknownReason = Enum.valueOf((Class) Utils.getClassForName("org.bukkit.event.inventory.InventoryCloseEvent$Reason"), "UNKNOWN"); } + /** + * ゲームバージョンに応じた MagmaCube の {@code EntityType} を取得します。 + *
+ * 26.x では {@code EntityType.MAGMA_CUBE} の静的フィールドが廃止され、 + * {@code BuiltInRegistries.ENTITY_TYPE} のレジストリから取得する必要があります。 + */ + private static Object getMagmaCubeType() throws ReflectiveOperationException { + // 1. EntityType.MAGMA_CUBE 静的フィールド(1.21.x 以前) + try { + return field(WORLD_ENTITY.getClass("EntityType")).modifiers(STATIC).name("MAGMA_CUBE") + .fieldType(WORLD_ENTITY.getClass("EntityType")).findFirst().get(null); + } catch (NullPointerException ignored) { } + + // 2. BuiltInRegistries.ENTITY_TYPE レジストリから取得(1.21.11+ / 26.x) + try { + var builtInRegistriesClass = Utils.getClassForName("net.minecraft.core.registries.BuiltInRegistries"); + var registry = builtInRegistriesClass.getField("ENTITY_TYPE").get(null); + Class> identifierClass; + try { + // 1.21.11 以降は ResourceLocation が Identifier に改名された + identifierClass = Utils.getClassForName("net.minecraft.resources.Identifier"); + } catch (IllegalArgumentException ignored) { + identifierClass = Utils.getClassForName("net.minecraft.resources.ResourceLocation"); + } + var parseMethod = identifierClass.getMethod("parse", String.class); + var magmaCubeKey = parseMethod.invoke(null, "minecraft:magma_cube"); + var getValueMethod = registry.getClass().getMethod("getValue", identifierClass); + return getValueMethod.invoke(registry, magmaCubeKey); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException ignored) { } + + // 3. CraftEntityType.bukkitToMinecraft から取得 + try { + var craftEntityTypeClass = Utils.getClassForName("org.bukkit.craftbukkit.entity.CraftEntityType"); + var bukkitToMinecraft = craftEntityTypeClass.getMethod("bukkitToMinecraft", org.bukkit.entity.EntityType.class); + return bukkitToMinecraft.invoke(null, org.bukkit.entity.EntityType.MAGMA_CUBE); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } + + throw new ReflectiveOperationException("Could not resolve EntityType for MagmaCube"); + } + @Override public void sendAllPacket(@NotNull Object packet) throws ReflectiveOperationException { var temp = new Object[] { packet }; diff --git a/src/main/java/com/github/yuttyann/scriptblockplus/utils/server/minecraft/PaperRemappedAccessor_v1_21_6.java b/src/main/java/com/github/yuttyann/scriptblockplus/utils/server/minecraft/PaperRemappedAccessor_v1_21_6.java index 33703bc0..4ba1eede 100644 --- a/src/main/java/com/github/yuttyann/scriptblockplus/utils/server/minecraft/PaperRemappedAccessor_v1_21_6.java +++ b/src/main/java/com/github/yuttyann/scriptblockplus/utils/server/minecraft/PaperRemappedAccessor_v1_21_6.java @@ -26,6 +26,7 @@ import static org.apache.commons.lang3.ArrayUtils.*; import static org.apache.commons.lang3.math.NumberUtils.*; +import java.lang.reflect.InvocationTargetException; import java.util.List; import org.bukkit.Bukkit; @@ -63,6 +64,19 @@ public class PaperRemappedAccessor_v1_21_6 implements NativeAccessor { private final Object blockPosZero; private final Object magmaCubeType; + /** + * ゲームバージョンに応じた MagmaCube クラスを取得します。 + *
+ * 26.2 以降は {@code net.minecraft.world.entity.monster.cubemob} パッケージに移動されました。 + */ + private static Class> getMagmaCubeClass() { + try { + return WORLD_ENTITY_MONSTER.getClass("MagmaCube"); + } catch (IllegalArgumentException e) { + return WORLD_ENTITY_MONSTER_CUBEMOB.getClass("MagmaCube"); + } + } + PaperRemappedAccessor_v1_21_6() throws ReflectiveOperationException { // sendPacket field(SERVER_LEVEL.getClass("ServerPlayer")) @@ -258,13 +272,13 @@ public class PaperRemappedAccessor_v1_21_6 implements NativeAccessor { .findFirst("ContainerMenu.checkReachable"); // newMagmaCube - construct(WORLD_ENTITY_MONSTER.getClass("MagmaCube")) + construct(getMagmaCubeClass()) .parameterTypes(WORLD_ENTITY.getClass("EntityType"), WORLD_LEVEL.getClass("Level")) .findFirst("MagmaCube"); // newCraftMagmaCube construct(ENTITY.getClass("CraftMagmaCube")) - .parameterTypes(CRAFTBUKKIT.getClass("CraftServer"), WORLD_ENTITY_MONSTER.getClass("MagmaCube")) + .parameterTypes(CRAFTBUKKIT.getClass("CraftServer"), getMagmaCubeClass()) .findFirst("CraftMagmaCube"); // newClientboundSetEntityDataPacket @@ -316,10 +330,50 @@ public class PaperRemappedAccessor_v1_21_6 implements NativeAccessor { this.m = methods(); this.c = constructs(); this.blockPosZero = c.newInstance("BlockPos", INT_ARRAY_ZERO_3); - this.magmaCubeType = field(WORLD_ENTITY.getClass("EntityType")).modifiers(STATIC).name("MAGMA_CUBE").fieldType(WORLD_ENTITY.getClass("EntityType")).findFirst().get(null); + this.magmaCubeType = getMagmaCubeType(); this.unknownReason = Enum.valueOf((Class) Utils.getClassForName("org.bukkit.event.inventory.InventoryCloseEvent$Reason"), "UNKNOWN"); } + /** + * ゲームバージョンに応じた MagmaCube の {@code EntityType} を取得します。 + *
+ * 26.x では {@code EntityType.MAGMA_CUBE} の静的フィールドが廃止され、 + * {@code BuiltInRegistries.ENTITY_TYPE} のレジストリから取得する必要があります。 + */ + private static Object getMagmaCubeType() throws ReflectiveOperationException { + // 1. EntityType.MAGMA_CUBE 静的フィールド(1.21.x 以前) + try { + return field(WORLD_ENTITY.getClass("EntityType")).modifiers(STATIC).name("MAGMA_CUBE") + .fieldType(WORLD_ENTITY.getClass("EntityType")).findFirst().get(null); + } catch (NullPointerException ignored) { } + + // 2. BuiltInRegistries.ENTITY_TYPE レジストリから取得(1.21.11+ / 26.x) + try { + var builtInRegistriesClass = Utils.getClassForName("net.minecraft.core.registries.BuiltInRegistries"); + var registry = builtInRegistriesClass.getField("ENTITY_TYPE").get(null); + Class> identifierClass; + try { + // 1.21.11 以降は ResourceLocation が Identifier に改名された + identifierClass = Utils.getClassForName("net.minecraft.resources.Identifier"); + } catch (IllegalArgumentException ignored) { + identifierClass = Utils.getClassForName("net.minecraft.resources.ResourceLocation"); + } + var parseMethod = identifierClass.getMethod("parse", String.class); + var magmaCubeKey = parseMethod.invoke(null, "minecraft:magma_cube"); + var getValueMethod = registry.getClass().getMethod("getValue", identifierClass); + return getValueMethod.invoke(registry, magmaCubeKey); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException ignored) { } + + // 3. CraftEntityType.bukkitToMinecraft から取得 + try { + var craftEntityTypeClass = Utils.getClassForName("org.bukkit.craftbukkit.entity.CraftEntityType"); + var bukkitToMinecraft = craftEntityTypeClass.getMethod("bukkitToMinecraft", org.bukkit.entity.EntityType.class); + return bukkitToMinecraft.invoke(null, org.bukkit.entity.EntityType.MAGMA_CUBE); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } + + throw new ReflectiveOperationException("Could not resolve EntityType for MagmaCube"); + } + @Override public void sendAllPacket(@NotNull Object packet) throws ReflectiveOperationException { var temp = new Object[] { packet }; diff --git a/src/main/java/com/github/yuttyann/scriptblockplus/utils/version/Version.java b/src/main/java/com/github/yuttyann/scriptblockplus/utils/version/Version.java index 4848a90a..41de4ec1 100644 --- a/src/main/java/com/github/yuttyann/scriptblockplus/utils/version/Version.java +++ b/src/main/java/com/github/yuttyann/scriptblockplus/utils/version/Version.java @@ -54,8 +54,25 @@ public static Version of(@NotNull String version) { if (dot1 < 0) { throw new IllegalArgumentException("Invalid Version: " + version); } - int part1 = parseInt(version, 0, dot1, 10), part2 = parseInt(version, dot1 + 1, dot2 < 0 ? version.length() : dot2, 10); - return of(part1, part2, dot2 >= 0 ? parseInt(version, dot2 + 1, version.length(), 10) : 0, qualifier); + int part1 = parseLeadingInt(version, 0, dot1); + int part2 = parseLeadingInt(version, dot1 + 1, dot2 < 0 ? version.length() : dot2); + int part3 = dot2 >= 0 ? parseLeadingInt(version, dot2 + 1, version.length()) : 0; + return of(part1, part2, part3, qualifier); + } + + /** + * 文字列の先頭から連続する数字を抽出して int に変換します。 + * 数字が1つも無い場合は0を返します。 + */ + private static int parseLeadingInt(@NotNull String s, int start, int end) { + int actualEnd = start; + while (actualEnd < end && Character.isDigit(s.charAt(actualEnd))) { + actualEnd++; + } + if (actualEnd == start) { + return 0; + } + return parseInt(s, start, actualEnd, 10); } @NotNull