diff --git a/.github/workflows/maven-pr.yml b/.github/workflows/maven-pr.yml
index cbec9093..ae3e65d1 100644
--- a/.github/workflows/maven-pr.yml
+++ b/.github/workflows/maven-pr.yml
@@ -76,6 +76,18 @@ jobs:
[ -f ~/.m2/repository/org/spigotmc/spigot/1.20.2-R0.1-SNAPSHOT/spigot-1.20.2-R0.1-SNAPSHOT-remapped-mojang.jar ] || java -jar BuildTools.jar --rev 1.20.2 --remapped
[ -f ~/.m2/repository/org/spigotmc/spigot/1.20.4-R0.1-SNAPSHOT/spigot-1.20.4-R0.1-SNAPSHOT-remapped-mojang.jar ] || java -jar BuildTools.jar --rev 1.20.4 --remapped
+ - name: Set up Java 21
+ uses: actions/setup-java@v4.2.1
+ with:
+ distribution: "temurin"
+ java-version: 21
+ cache: "maven"
+
+ - name: Build Spigot versions with JDK 21
+ run: |
+ cd BuildTools
+ [ -f ~/.m2/repository/org/spigotmc/spigot/1.20.6-R0.1-SNAPSHOT/spigot-1.20.6-R0.1-SNAPSHOT-remapped-mojang.jar ] || java -jar BuildTools.jar --rev 1.20.6 --remapped
+
- name: Build with Maven
run: mvn -D"http.keepAlive=false" -D"maven.wagon.http.pool=false" -D"maven.wagon.httpconnectionManager.ttlSeconds=120" "-Dhttps.protocols=TLSv1.2" -DskipTests=true "-Dmaven.javadoc.skip=true" -B clean package
diff --git a/README.md b/README.md
index f183f6e4..33a631aa 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ This Bukkit (compatible with CraftBukkit, Spigot, Paper) plugin adds a way to ob
- Economy addon [SilkSpawnersEcoAddon](https://dev.bukkit.org/projects/silkspawnersecoaddon)
- Shop addon [SilkSpawnersShopAddon](https://spigotmc.org/resources/12028/) (login required, Premium Plugin)
- BossBarAPI support for >= 1.9, otherwise BarAPI can be used
-- Support for multiple Minecraft versions, from 1.8.8 to 1.20.4 (with exlusion of 1.9 and 1.10)
+- Support for multiple Minecraft versions, from 1.8.8 to 1.20.6 (with exlusion of 1.9 and 1.10)
_Third party features, all of them can be disabled_
@@ -315,6 +315,7 @@ Unfortunately, I can't give access to https://repo.dustplanet.de/artifactory/pri
mkdir -p BuildTools
cd BuildTools
wget -q https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar
+java -jar BuildTools.jar --rev 1.20.6 --remapped
java -jar BuildTools.jar --rev 1.20.4 --remapped
java -jar BuildTools.jar --rev 1.20.2 --remapped
java -jar BuildTools.jar --rev 1.20.1 --remapped
diff --git a/modules/API/pom.xml b/modules/API/pom.xml
index 4fd5ea9b..490ec7e8 100644
--- a/modules/API/pom.xml
+++ b/modules/API/pom.xml
@@ -23,7 +23,7 @@
org.spigotmc
spigot-api
- 1.20.4-R0.1-SNAPSHOT
+ 1.20.6-R0.1-SNAPSHOT
diff --git a/modules/SilkSpawners/pom.xml b/modules/SilkSpawners/pom.xml
index f2ce6736..064dffd1 100644
--- a/modules/SilkSpawners/pom.xml
+++ b/modules/SilkSpawners/pom.xml
@@ -42,7 +42,7 @@
org.spigotmc
spigot-api
- 1.20.4-R0.1-SNAPSHOT
+ 1.20.6-R0.1-SNAPSHOT
com.sk89q
@@ -198,6 +198,11 @@
silkspawners-v1_20_R3
8.0.1-SNAPSHOT
+
+ de.dustplanet
+ silkspawners-v1_20_R4
+ 8.0.1-SNAPSHOT
+
de.dustplanet
silkspawners-API
diff --git a/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/SilkSpawners.java b/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/SilkSpawners.java
index d5ea0fe0..2794c514 100644
--- a/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/SilkSpawners.java
+++ b/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/SilkSpawners.java
@@ -55,7 +55,7 @@ public class SilkSpawners extends JavaPlugin {
private static final int BSTATS_PLUGIN_ID = 273;
private static final String[] COMPATIBLE_MINECRAFT_VERSIONS = { "v1_8_R3", "v1_11_R1", "v1_12_R1", "v1_13_R2", "v1_14_R1", "v1_15_R1",
"v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1", "v1_18_R2", "v1_19_R1", "v1_19_R2", "v1_19_R3", "v1_20_R1",
- "v1_20_R2", "v1_20_R3" };
+ "v1_20_R2", "v1_20_R3", "v1_20_R4" };
public CommentedConfiguration config;
public CommentedConfiguration localization;
@Getter
diff --git a/modules/v1_20_R4/pom.xml b/modules/v1_20_R4/pom.xml
new file mode 100644
index 00000000..86816ba1
--- /dev/null
+++ b/modules/v1_20_R4/pom.xml
@@ -0,0 +1,72 @@
+
+ 4.0.0
+ silkspawners-v1_20_R4
+ jar
+ SilkSpawners for v1_20_R4
+
+
+ de.dustplanet
+ silkspawners-parent
+ 8.0.1-SNAPSHOT
+ ../../
+
+
+
+
+ org.spigotmc
+ spigot
+ 1.20.6-R0.1-SNAPSHOT
+ provided
+ remapped-mojang
+
+
+ org.spigotmc
+ minecraft-server
+
+
+
+
+ de.dustplanet
+ silkspawners-API
+ 8.0.1-SNAPSHOT
+
+
+
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ 2.0.3
+
+
+ package
+
+ remap
+
+ remap-mojang
+
+ org.spigotmc:minecraft-server:1.20.6-R0.1-SNAPSHOT:txt:maps-mojang
+ true
+ org.spigotmc:spigot:1.20.6-R0.1-SNAPSHOT:jar:remapped-mojang
+ true
+ remapped-mojang
+
+
+
+ package
+
+ remap
+
+ remap-spigot
+
+ ${project.build.directory}/${project.artifactId}-${project.version}-remapped-mojang.jar
+ org.spigotmc:minecraft-server:1.20.6-R0.1-SNAPSHOT:csrg:maps-spigot
+ org.spigotmc:spigot:1.20.6-R0.1-SNAPSHOT:jar:remapped-mojang
+
+
+
+
+
+
+
diff --git a/modules/v1_20_R4/src/main/java/de/dustplanet/silkspawners/compat/v1_20_R4/NMSHandler.java b/modules/v1_20_R4/src/main/java/de/dustplanet/silkspawners/compat/v1_20_R4/NMSHandler.java
new file mode 100644
index 00000000..7cfd1bb2
--- /dev/null
+++ b/modules/v1_20_R4/src/main/java/de/dustplanet/silkspawners/compat/v1_20_R4/NMSHandler.java
@@ -0,0 +1,533 @@
+package de.dustplanet.silkspawners.compat.v1_20_R4;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarStyle;
+import org.bukkit.boss.BossBar;
+import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
+import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
+import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockEntityState;
+import org.bukkit.craftbukkit.v1_20_R4.block.CraftCreatureSpawner;
+import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
+import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
+import org.bukkit.entity.Player;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.spigotmc.SpigotWorldConfig;
+
+import com.google.common.base.CaseFormat;
+import com.mojang.authlib.GameProfile;
+
+import de.dustplanet.silkspawners.compat.api.NMSProvider;
+import net.minecraft.core.Registry;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientGamePacketListener;
+import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ClientInformation;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.network.ServerPlayerConnection;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.component.CustomData;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.entity.BlockEntityType;
+import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
+
+public class NMSHandler implements NMSProvider {
+ private Field tileField;
+ private final Collection spawnEggs = Arrays.stream(Material.values())
+ .filter(material -> material.name().endsWith("_SPAWN_EGG")).collect(Collectors.toList());
+
+ public NMSHandler() {
+ this(true);
+ }
+
+ public NMSHandler(final boolean checkForNerfFlags) {
+ try {
+ tileField = CraftCreatureSpawner.class.getDeclaredField("snapshot");
+ tileField.setAccessible(true);
+ } catch (SecurityException | NoSuchFieldException e) {
+ try {
+ tileField = CraftBlockEntityState.class.getDeclaredField("snapshot");
+ tileField.setAccessible(true);
+ } catch (NoSuchFieldException | SecurityException e1) {
+ Bukkit.getLogger().warning("[SilkSpawners] Reflection failed: " + e.getMessage() + " " + e1.getMessage());
+ e.printStackTrace();
+ e1.printStackTrace();
+ }
+ }
+
+ if (checkForNerfFlags) {
+ @SuppressWarnings("resource")
+ final ServerLevel handle = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle();
+
+ try {
+ final SpigotWorldConfig spigotConfig = handle.spigotConfig;
+ if (spigotConfig.nerfSpawnerMobs) {
+ Bukkit.getLogger().warning(
+ "[SilkSpawners] Warning! \"nerf-spawner-mobs\" is set to true in the spigot.yml! Spawned mobs WON'T HAVE ANY AI!");
+ }
+ } catch (@SuppressWarnings("unused") final NoSuchFieldError e) {
+ // Silence
+ }
+
+ try {
+ final Field paperConfigField = Level.class.getDeclaredField("paperConfig");
+ paperConfigField.setAccessible(true);
+
+ final Field ironGolemsCanSpawnInAirField = paperConfigField.getType().getDeclaredField("ironGolemsCanSpawnInAir");
+ ironGolemsCanSpawnInAirField.setAccessible(true);
+ if (!ironGolemsCanSpawnInAirField.getBoolean(paperConfigField.get(handle))) {
+ Bukkit.getLogger().warning(
+ "[SilkSpawners] Warning! \"iron-golems-can-spawn-in-air\" is set to false in the paper.yml! Iron Golem farms might not work!");
+ }
+ } catch (@SuppressWarnings("unused") final NoSuchFieldError | IllegalArgumentException | IllegalAccessException
+ | NoSuchFieldException | SecurityException e) {
+ // Silence
+ }
+ }
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public void spawnEntity(final org.bukkit.World w, final String entityID, final double x, final double y, final double z,
+ final Player player) {
+ final CompoundTag tag = new CompoundTag();
+ tag.putString("id", entityID);
+
+ final ServerLevel world = ((CraftWorld) w).getHandle();
+ final Optional entity = EntityType.create(tag, world);
+
+ if (!entity.isPresent()) {
+ Bukkit.getLogger().warning("[SilkSpawners] Failed to spawn, falling through. You should report this (entity == null)!");
+ return;
+ }
+
+ final float yaw = world.random.nextFloat() * (-180 - 180) + 180;
+ entity.get().moveTo(x, y, z, yaw, 0);
+ ((CraftWorld) w).addEntity(entity.get(), SpawnReason.SPAWNER_EGG);
+ final Packet rotationPacket = new ClientboundRotateHeadPacket(entity.get(), (byte) yaw);
+ final ServerPlayerConnection connection = ((CraftPlayer) player).getHandle().connection;
+ connection.send(rotationPacket);
+ }
+
+ @Override
+ public List rawEntityMap() {
+ final List entities = new ArrayList<>();
+ try {
+ final Registry> entityTypeRegistry = BuiltInRegistries.ENTITY_TYPE;
+ for (final EntityType> next : entityTypeRegistry) {
+ entities.add(EntityType.getKey(next).getPath());
+ }
+ } catch (SecurityException | IllegalArgumentException e) {
+ Bukkit.getLogger().severe("[SilkSpawners] Failed to dump entity map: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return entities;
+ }
+
+ @Override
+ public String getMobNameOfSpawner(final BlockState blockState) {
+ final CraftCreatureSpawner spawner = (CraftCreatureSpawner) blockState;
+ try {
+ final SpawnerBlockEntity tile = (SpawnerBlockEntity) tileField.get(spawner);
+ final CompoundTag resourceLocation = tile.getSpawner().nextSpawnData.entityToSpawn();
+ return resourceLocation != null ? resourceLocation.getString("id").replace("minecraft:", "") : "";
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ Bukkit.getLogger().warning("[SilkSpawners] Reflection failed: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ @Override
+ public void setSpawnersUnstackable() {
+ final ResourceLocation resourceLocation = new ResourceLocation(NAMESPACED_SPAWNER_ID);
+ final Registry- itemRegistry = BuiltInRegistries.ITEM;
+ final Item spawner = itemRegistry.get(resourceLocation);
+ try {
+ final Field maxStackSize = Item.class.getDeclaredField("maxStackSize");
+ maxStackSize.setAccessible(true);
+ maxStackSize.set(spawner, 1);
+ } catch (@SuppressWarnings("unused") NoSuchFieldException | SecurityException | IllegalArgumentException
+ | IllegalAccessException e) {
+ try {
+ // int maxStackSize -> d
+ final Field maxStackSize = Item.class.getDeclaredField("d");
+ maxStackSize.setAccessible(true);
+ maxStackSize.set(spawner, 1);
+ } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public boolean setMobNameOfSpawner(final BlockState blockState, final String mobID) {
+ // Prevent ResourceKeyInvalidException: Non [a-z0-9/._-] character in path of location
+ final String safeMobID = caseFormatOf(mobID.replace(" ", "_")).to(CaseFormat.LOWER_UNDERSCORE, mobID.replace(" ", "_"))
+ .toLowerCase(Locale.ENGLISH);
+ final CraftCreatureSpawner spawner = (CraftCreatureSpawner) blockState;
+
+ try {
+ final SpawnerBlockEntity tile = (SpawnerBlockEntity) tileField.get(spawner);
+ final Registry> entityTypeRegistry = BuiltInRegistries.ENTITY_TYPE;
+ final ResourceLocation resourceLocation = new ResourceLocation(safeMobID);
+ tile.getSpawner().setEntityId(entityTypeRegistry.get(resourceLocation), spawner.getWorldHandle().getMinecraftWorld(),
+ spawner.getWorldHandle().getRandom(), spawner.getPosition());
+ return true;
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ Bukkit.getLogger().warning("[SilkSpawners] Reflection failed: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ @Override
+ public ItemStack setNBTEntityID(final ItemStack item, final String entity) {
+ if (item == null || StringUtils.isBlank(entity)) {
+ Bukkit.getLogger().warning("[SilkSpawners] Skipping invalid spawner to set NBT data on.");
+ return null;
+ }
+
+ String prefixedEntity;
+ if (!entity.startsWith("minecraft:")) {
+ prefixedEntity = "minecraft:" + entity;
+ } else {
+ prefixedEntity = entity;
+ }
+
+ net.minecraft.world.item.ItemStack itemStack = null;
+ final CraftItemStack craftStack = CraftItemStack.asCraftCopy(item);
+ itemStack = CraftItemStack.asNMSCopy(craftStack);
+ final CustomData blockData = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
+ CompoundTag tag = blockData.copyTag();
+
+ // Check for SilkSpawners key
+ if (!tag.contains("SilkSpawners")) {
+ tag.put("SilkSpawners", new CompoundTag());
+ }
+
+ tag.getCompound("SilkSpawners").putString("entity", entity);
+
+ // Check for Vanilla keys
+ if (!tag.contains("BlockEntityTag")) {
+ tag.put("BlockEntityTag", new CompoundTag());
+ }
+
+ tag = tag.getCompound("BlockEntityTag");
+
+ // EntityId - Deprecated in 1.9
+ tag.putString("EntityId", entity);
+ tag.putString("id", BlockEntityType.getKey(BlockEntityType.MOB_SPAWNER).getPath());
+
+ // SpawnData
+ if (!tag.contains("SpawnData")) {
+ tag.put("SpawnData", new CompoundTag());
+ }
+
+ final CompoundTag spawnDataTag = tag.getCompound("SpawnData");
+ if (!spawnDataTag.contains("entity")) {
+ spawnDataTag.put("entity", new CompoundTag());
+ }
+ spawnDataTag.getCompound("entity").putString("id", prefixedEntity);
+
+ if (!tag.contains("SpawnPotentials")) {
+ tag.put("SpawnPotentials", new CompoundTag());
+ }
+
+ // SpawnEgg data
+ if (!tag.contains("EntityTag")) {
+ tag.put("EntityTag", new CompoundTag());
+ }
+ tag.getCompound("EntityTag").putString("id", prefixedEntity);
+
+ itemStack.set(DataComponents.BLOCK_ENTITY_DATA, CustomData.of(tag));
+ return CraftItemStack.asCraftMirror(itemStack);
+ }
+
+ @Override
+ @Nullable
+ public String getSilkSpawnersNBTEntityID(final ItemStack item) {
+ net.minecraft.world.item.ItemStack itemStack = null;
+ final CraftItemStack craftStack = CraftItemStack.asCraftCopy(item);
+ itemStack = CraftItemStack.asNMSCopy(craftStack);
+ final CustomData blockEntityData = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
+ final CompoundTag tag = blockEntityData.copyTag();
+
+ if (tag == null || !tag.contains("SilkSpawners")) {
+ return null;
+ }
+ return tag.getCompound("SilkSpawners").getString("entity");
+ }
+
+ @Override
+ @Nullable
+ public String getVanillaNBTEntityID(final ItemStack item) {
+ net.minecraft.world.item.ItemStack itemStack = null;
+ final CraftItemStack craftStack = CraftItemStack.asCraftCopy(item);
+ itemStack = CraftItemStack.asNMSCopy(craftStack);
+ final CustomData blockEntityData = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
+ CompoundTag tag = blockEntityData.copyTag();
+
+ if (tag == null || !tag.contains("BlockEntityTag")) {
+ return null;
+ }
+
+ tag = tag.getCompound("BlockEntityTag");
+ if (tag.contains("EntityId")) {
+ return tag.getString("EntityId");
+ } else if (tag.contains("SpawnData") && tag.getCompound("SpawnData").contains("id")) {
+ return tag.getCompound("SpawnData").getString("id");
+ } else if (tag.contains("SpawnData") && tag.getCompound("SpawnData").contains("entity")
+ && tag.getCompound("SpawnData").getCompound("entity").contains("id")) {
+ return tag.getCompound("SpawnData").getCompound("entity").getString("id");
+ } else if (tag.contains("SpawnPotentials") && !tag.getList("SpawnPotentials", 8).isEmpty()) {
+ return tag.getList("SpawnPotentials", 8).getCompound(0).getCompound("Entity").getString("id");
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the spawner block the player is looking at, or null if isn't.
+ *
+ * @param player the player
+ * @param distance the reach distance
+ * @return the found block or null
+ */
+ @Override
+ public Block getSpawnerFacing(final Player player, final int distance) {
+ final Block block = player.getTargetBlock((Set) null, distance);
+ if (block == null || block.getType() != Material.SPAWNER) {
+ return null;
+ }
+ return block;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public ItemStack newEggItem(final String entityID, final int amount, final String displayName) {
+ Material spawnEgg = Material.matchMaterial(entityID.toUpperCase() + "_SPAWN_EGG");
+ if (spawnEgg == null) {
+ spawnEgg = Material.LEGACY_MONSTER_EGG;
+ }
+
+ final ItemStack item = new ItemStack(spawnEgg, amount);
+ if (displayName != null) {
+ final ItemMeta itemMeta = item.getItemMeta();
+ itemMeta.setDisplayName(displayName);
+ item.setItemMeta(itemMeta);
+ }
+ net.minecraft.world.item.ItemStack itemStack = null;
+ final CraftItemStack craftStack = CraftItemStack.asCraftCopy(item);
+ itemStack = CraftItemStack.asNMSCopy(craftStack);
+ final CustomData blockEntityData = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
+ final CompoundTag tag = blockEntityData.copyTag();
+
+ if (!tag.contains("SilkSpawners")) {
+ tag.put("SilkSpawners", new CompoundTag());
+ }
+
+ tag.getCompound("SilkSpawners").putString("entity", entityID);
+
+ if (!tag.contains("EntityTag")) {
+ tag.put("EntityTag", new CompoundTag());
+ }
+
+ String prefixedEntity;
+ if (!entityID.startsWith("minecraft:")) {
+ prefixedEntity = "minecraft:" + entityID;
+ } else {
+ prefixedEntity = entityID;
+ }
+ tag.getCompound("EntityTag").putString("id", prefixedEntity);
+
+ itemStack.set(DataComponents.BLOCK_ENTITY_DATA, CustomData.of(tag));
+ return CraftItemStack.asCraftMirror(itemStack);
+ }
+
+ @Override
+ public String getVanillaEggNBTEntityID(final ItemStack item) {
+ net.minecraft.world.item.ItemStack itemStack = null;
+ final CraftItemStack craftStack = CraftItemStack.asCraftCopy(item);
+ itemStack = CraftItemStack.asNMSCopy(craftStack);
+ final CustomData blockEntityData = itemStack.getOrDefault(DataComponents.BLOCK_ENTITY_DATA, CustomData.EMPTY);
+ CompoundTag tag = blockEntityData.copyTag();
+
+ if (tag == null || !tag.contains("EntityTag")) {
+ final Registry
- itemRegistry = BuiltInRegistries.ITEM;
+ final ResourceLocation vanillaKey = itemRegistry.getKey(itemStack.getItem());
+ if (vanillaKey != null) {
+ return vanillaKey.getPath().replace("minecraft:", "").replace("_spawn_egg", "");
+ }
+ } else {
+ tag = tag.getCompound("EntityTag");
+ if (tag.contains("id")) {
+ return tag.getString("id").replace("minecraft:", "");
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void displayBossBar(final String title, final String colorName, final String styleName, final Player player, final Plugin plugin,
+ final int period) {
+ final BarColor color = BarColor.valueOf(colorName.toUpperCase());
+ final BarStyle style = BarStyle.valueOf(styleName.toUpperCase());
+ final BossBar bar = Bukkit.createBossBar(title, color, style);
+ bar.addPlayer(player);
+ bar.setVisible(true);
+ final double interval = 1.0 / (period * 20L);
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ final double progress = bar.getProgress();
+ final double newProgress = progress - interval;
+ if (progress <= 0.0 || newProgress <= 0.0) {
+ bar.setVisible(false);
+ bar.removeAll();
+ this.cancel();
+ } else {
+ bar.setProgress(newProgress);
+ }
+ }
+ }.runTaskTimerAsynchronously(plugin, 0, 1L);
+ }
+
+ @Override
+ public Player getPlayer(final String playerUUIDOrName) {
+ try {
+ final UUID playerUUID = UUID.fromString(playerUUIDOrName);
+ return Bukkit.getPlayer(playerUUID);
+ } catch (@SuppressWarnings("unused") final IllegalArgumentException e) {
+ for (final Player onlinePlayer : Bukkit.getOnlinePlayers()) {
+ if (onlinePlayer.getName().equalsIgnoreCase(playerUUIDOrName)) {
+ return onlinePlayer;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public ItemStack getItemInHand(final Player player) {
+ return player.getInventory().getItemInMainHand();
+ }
+
+ @Override
+ public void reduceEggs(final Player player) {
+ final ItemStack itemInMainHand = player.getInventory().getItemInMainHand();
+ final ItemStack itemInOffHand = player.getInventory().getItemInOffHand();
+ ItemStack eggs;
+ if (getSpawnEggMaterials().contains(itemInMainHand.getType())) {
+ eggs = itemInMainHand;
+ if (eggs.getAmount() == 1) {
+ player.getInventory().setItemInMainHand(null);
+ } else {
+ eggs.setAmount(eggs.getAmount() - 1);
+ player.getInventory().setItemInMainHand(eggs);
+ }
+ } else {
+ eggs = itemInOffHand;
+ if (eggs.getAmount() == 1) {
+ player.getInventory().setItemInOffHand(null);
+ } else {
+ eggs.setAmount(eggs.getAmount() - 1);
+ player.getInventory().setItemInOffHand(eggs);
+ }
+ }
+ }
+
+ @Override
+ public ItemStack getSpawnerItemInHand(final Player player) {
+ final PlayerInventory inv = player.getInventory();
+ final ItemStack mainHand = inv.getItemInMainHand();
+ final ItemStack offHand = inv.getItemInOffHand();
+ if ((getSpawnEggMaterials().contains(mainHand.getType()) || mainHand.getType() == Material.SPAWNER)
+ && (getSpawnEggMaterials().contains(offHand.getType()) || offHand.getType() == Material.SPAWNER)) {
+ return null; // not determinable
+ } else if (getSpawnEggMaterials().contains(mainHand.getType()) || mainHand.getType() == Material.SPAWNER) {
+ return mainHand;
+ } else if (getSpawnEggMaterials().contains(offHand.getType()) || offHand.getType() == Material.SPAWNER) {
+ return offHand;
+ }
+ return null;
+ }
+
+ @Override
+ public void setSpawnerItemInHand(final Player player, final ItemStack newItem) {
+ final PlayerInventory inv = player.getInventory();
+ final ItemStack mainHand = inv.getItemInMainHand();
+ final ItemStack offHand = inv.getItemInOffHand();
+ if ((getSpawnEggMaterials().contains(mainHand.getType()) || mainHand.getType() == Material.SPAWNER)
+ && (getSpawnEggMaterials().contains(offHand.getType()) || offHand.getType() == Material.SPAWNER)) {
+ return; // not determinable
+ } else if (getSpawnEggMaterials().contains(mainHand.getType()) || mainHand.getType() == Material.SPAWNER) {
+ inv.setItemInMainHand(newItem);
+ } else if (getSpawnEggMaterials().contains(offHand.getType()) || offHand.getType() == Material.SPAWNER) {
+ inv.setItemInOffHand(newItem);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Material getSpawnEggMaterial() {
+ return Material.LEGACY_MONSTER_EGG;
+ }
+
+ @Override
+ public Collection getSpawnEggMaterials() {
+ return spawnEggs;
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public Player loadPlayer(final OfflinePlayer offline) {
+ if (!offline.hasPlayedBefore()) {
+ return null;
+ }
+
+ final GameProfile profile = new GameProfile(offline.getUniqueId(),
+ offline.getName() != null ? offline.getName() : offline.getUniqueId().toString());
+ final MinecraftServer server = ((CraftServer) Bukkit.getServer()).getServer();
+ final ServerPlayer entity = new ServerPlayer(server, server.getLevel(Level.OVERWORLD), profile, ClientInformation.createDefault());
+
+ final Player target = entity.getBukkitEntity();
+ if (target != null) {
+ target.loadData();
+ }
+ return target;
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index bc392147..f3065690 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,7 @@
modules/v1_20_R1
modules/v1_20_R2
modules/v1_20_R3
+ modules/v1_20_R4
modules/SilkSpawners