From f98919fc7123afc5c80b4548683fa29bb22e847f Mon Sep 17 00:00:00 2001 From: Tim Brust Date: Wed, 18 Dec 2024 18:19:16 +0100 Subject: [PATCH] feat: adds inital support for Minecraft 1.21.4 --- .github/workflows/maven-pr.yml | 1 + README.md | 3 +- modules/API/pom.xml | 2 +- modules/SilkSpawners/pom.xml | 7 +- .../dustplanet/silkspawners/SilkSpawners.java | 3 +- .../dustplanet/silkspawners/configs/Mobs.java | 49 ++ .../SilkSpawners/src/main/resources/mobs.yml | 48 +- modules/v1_21_R3/pom.xml | 72 +++ .../compat/v1_21_R3/NMSHandler.java | 551 ++++++++++++++++++ pom.xml | 1 + 10 files changed, 732 insertions(+), 5 deletions(-) create mode 100644 modules/v1_21_R3/pom.xml create mode 100644 modules/v1_21_R3/src/main/java/de/dustplanet/silkspawners/compat/v1_21_R3/NMSHandler.java diff --git a/.github/workflows/maven-pr.yml b/.github/workflows/maven-pr.yml index 6a511bd8..0d89ed15 100644 --- a/.github/workflows/maven-pr.yml +++ b/.github/workflows/maven-pr.yml @@ -88,6 +88,7 @@ jobs: 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 [ -f ~/.m2/repository/org/spigotmc/spigot/1.21.1-R0.1-SNAPSHOT/spigot-1.21.1-R0.1-SNAPSHOT-remapped-mojang.jar ] || java -jar BuildTools.jar --rev 1.21.1 --remapped + [ -f ~/.m2/repository/org/spigotmc/spigot/1.21.4-R0.1-SNAPSHOT/spigot-1.21.4-R0.1-SNAPSHOT-remapped-mojang.jar ] || java -jar BuildTools.jar --rev 1.21.4 --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 265ba4e2..9c299b31 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This Bukkit (compatible with CraftBukkit, Spigot, Paper) plugin adds a way to ob - Shop addon [SilkSpawnersShopAddon](https://spigotmc.org/resources/12028/) (login required, Premium Plugin) - BossBarAPI support for >= 1.9, otherwise BarAPI can be used - Mimic support -- Support for multiple Minecraft versions, from 1.8.8 to 1.21.1 (with exlusion of 1.9 and 1.10) +- Support for multiple Minecraft versions, from 1.8.8 to 1.21.4 (with exlusion of 1.9 and 1.10) _Third party features, all of them can be disabled_ @@ -319,6 +319,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.21.4 --remapped java -jar BuildTools.jar --rev 1.21.1 --remapped java -jar BuildTools.jar --rev 1.20.6 --remapped java -jar BuildTools.jar --rev 1.20.4 --remapped diff --git a/modules/API/pom.xml b/modules/API/pom.xml index ddf8e1c0..0a23111c 100644 --- a/modules/API/pom.xml +++ b/modules/API/pom.xml @@ -23,7 +23,7 @@ org.spigotmc spigot-api - 1.21.1-R0.1-SNAPSHOT + 1.21.4-R0.1-SNAPSHOT diff --git a/modules/SilkSpawners/pom.xml b/modules/SilkSpawners/pom.xml index b721c73b..a8361135 100644 --- a/modules/SilkSpawners/pom.xml +++ b/modules/SilkSpawners/pom.xml @@ -41,7 +41,7 @@ org.spigotmc spigot-api - 1.21.1-R0.1-SNAPSHOT + 1.21.4-R0.1-SNAPSHOT com.sk89q @@ -213,6 +213,11 @@ silkspawners-v1_21_R1 8.1.1-SNAPSHOT + + de.dustplanet + silkspawners-v1_21_R3 + 8.1.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 fd8da258..67792f99 100644 --- a/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/SilkSpawners.java +++ b/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/SilkSpawners.java @@ -61,12 +61,13 @@ 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_R4", "v1_21_R1" }; + "v1_20_R2", "v1_20_R3", "v1_20_R4", "v1_21_R1", "v1_21_R3" }; public static final Map PROTOCOL_VERSION_PACKAGE_MAP = new HashMap() { private static final long serialVersionUID = -5188779509588704507L; { put(766, "v1_20_R4"); put(767, "v1_21_R1"); + put(769, "v1_21_R3"); } }; public CommentedConfiguration config; diff --git a/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/configs/Mobs.java b/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/configs/Mobs.java index 07789887..8888a340 100644 --- a/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/configs/Mobs.java +++ b/modules/SilkSpawners/src/main/java/de/dustplanet/silkspawners/configs/Mobs.java @@ -971,6 +971,15 @@ private void loadDefaultMobs() { config.addDefault("creatures.breeze.displayName", "Breeze"); config.options().copyDefaults(true); tempList = new ArrayList<>(); + // Creaking + tempList.add("groan"); + config.addDefault("creatures.creaking.aliases", tempList); + config.addDefault("creatures.creaking.enable", true); + config.addDefault("creatures.creaking.enableCraftingSpawner", true); + config.addDefault("creatures.creaking.enableSpawnEggOverride", true); + config.addDefault("creatures.creaking.displayName", "Creaking"); + config.options().copyDefaults(true); + tempList = new ArrayList<>(); // Non-mob vanilla entities below // Item @@ -1125,5 +1134,45 @@ private void loadDefaultMobs() { config.addDefault("creatures.ominous_item_spawner.enable", false); // wind_charge config.addDefault("creatures.wind_charge.enable", false); + // acacia_boat + config.addDefault("creatures.acacia_boat.enable", false); + // acacia_chest_boat + config.addDefault("creatures.acacia_chest_boat.enable", false); + // bamboo_chest_raft + config.addDefault("creatures.bamboo_chest_raft.enable", false); + // bamboo_raft + config.addDefault("creatures.bamboo_raft.enable", false); + // birch_boat + config.addDefault("creatures.birch_boat.enable", false); + // birch_chest_boat + config.addDefault("creatures.birch_chest_boat.enable", false); + // cherry_boat + config.addDefault("creatures.cherry_boat.enable", false); + // cherry_chest_boat + config.addDefault("creatures.cherry_chest_boat.enable", false); + // dark_oak_boat + config.addDefault("creatures.dark_oak_boat.enable", false); + // dark_oak_chest_boat + config.addDefault("creatures.dark_oak_chest_boat.enable", false); + // jungle_boat + config.addDefault("creatures.jungle_boat.enable", false); + // jungle_chest_boat + config.addDefault("creatures.jungle_chest_boat.enable", false); + // mangrove_boat + config.addDefault("creatures.mangrove_boat.enable", false); + // mangrove_chest_boat + config.addDefault("creatures.mangrove_chest_boat.enable", false); + // oak_boat + config.addDefault("creatures.oak_boat.enable", false); + // oak_chest_boat + config.addDefault("creatures.oak_chest_boat.enable", false); + // pale_oak_boat + config.addDefault("creatures.pale_oak_boat.enable", false); + // pale_oak_chest_boat + config.addDefault("creatures.pale_oak_chest_boat.enable", false); + // spruce_boat + config.addDefault("creatures.spruce_boat.enable", false); + // spruce_chest_boat + config.addDefault("creatures.spruce_chest_boat.enable", false); } } diff --git a/modules/SilkSpawners/src/main/resources/mobs.yml b/modules/SilkSpawners/src/main/resources/mobs.yml index a40ea936..65724651 100644 --- a/modules/SilkSpawners/src/main/resources/mobs.yml +++ b/modules/SilkSpawners/src/main/resources/mobs.yml @@ -1,7 +1,6 @@ # The creatures key is official creature type name (mobID), case-sensitive. # Taken from https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html creatures: - # Vanilla mobs, taken from https://minecraft.gamepedia.com/Java_Edition_data_values#Entities for 1.13+ # and https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening/Entity_IDs for up to 1.12 Creeper: @@ -890,6 +889,13 @@ creatures: enableCraftingSpawner: true enableSpawnEggOverride: true displayName: Breeze + creaking: + aliases: + - groan + enable: true + enableCraftingSpawner: true + enableSpawnEggOverride: true + displayName: Creaking # Non-mob vanilla entities # Enable on your own risk, some might work, some not! @@ -1079,3 +1085,43 @@ creatures: enable: false wind_charge: enable: false + acacia_boat: + enable: false + acacia_chest_boat: + enable: false + bamboo_chest_raft: + enable: false + bamboo_raft: + enable: false + birch_boat: + enable: false + birch_chest_boat: + enable: false + cherry_boat: + enable: false + cherry_chest_boat: + enable: false + dark_oak_boat: + enable: false + dark_oak_chest_boat: + enable: false + jungle_boat: + enable: false + jungle_chest_boat: + enable: false + mangrove_boat: + enable: false + mangrove_chest_boat: + enable: false + oak_boat: + enable: false + oak_chest_boat: + enable: false + pale_oak_boat: + enable: false + pale_oak_chest_boat: + enable: false + spruce_boat: + enable: false + spruce_chest_boat: + enable: false diff --git a/modules/v1_21_R3/pom.xml b/modules/v1_21_R3/pom.xml new file mode 100644 index 00000000..f6ade9ed --- /dev/null +++ b/modules/v1_21_R3/pom.xml @@ -0,0 +1,72 @@ + + 4.0.0 + silkspawners-v1_21_R3 + jar + SilkSpawners for v1_21_R3 + + + de.dustplanet + silkspawners-parent + 8.1.1-SNAPSHOT + ../../ + + + + + org.spigotmc + spigot + 1.21.4-R0.1-SNAPSHOT + provided + remapped-mojang + + + org.spigotmc + minecraft-server + + + + + de.dustplanet + silkspawners-API + 8.1.1-SNAPSHOT + + + + + + + net.md-5 + specialsource-maven-plugin + 2.0.3 + + + package + + remap + + remap-mojang + + org.spigotmc:minecraft-server:1.21.4-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.21.4-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.21.4-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.21.4-R0.1-SNAPSHOT:jar:remapped-mojang + + + + + + + diff --git a/modules/v1_21_R3/src/main/java/de/dustplanet/silkspawners/compat/v1_21_R3/NMSHandler.java b/modules/v1_21_R3/src/main/java/de/dustplanet/silkspawners/compat/v1_21_R3/NMSHandler.java new file mode 100644 index 00000000..a1799e70 --- /dev/null +++ b/modules/v1_21_R3/src/main/java/de/dustplanet/silkspawners/compat/v1_21_R3/NMSHandler.java @@ -0,0 +1,551 @@ +package de.dustplanet.silkspawners.compat.v1_21_R3; + +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_21_R3.CraftServer; +import org.bukkit.craftbukkit.v1_21_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R3.block.CraftBlockEntityState; +import org.bukkit.craftbukkit.v1_21_R3.block.CraftCreatureSpawner; +import org.bukkit.craftbukkit.v1_21_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_21_R3.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.Holder.Reference; +import net.minecraft.core.Registry; +import net.minecraft.core.component.DataComponentMap; +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.EntitySpawnReason; +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, EntitySpawnReason.SPAWN_ITEM_USE); + + 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 = ResourceLocation.fromNamespaceAndPath("minecraft", NAMESPACED_SPAWNER_ID); + final Registry itemRegistry = BuiltInRegistries.ITEM; + final Optional> spawner = itemRegistry.get(resourceLocation); + if (spawner.isEmpty()) { + return; + } + final DataComponentMap currentComponents = spawner.get().value().components(); + final DataComponentMap updatedComponents = DataComponentMap.composite(currentComponents, + DataComponentMap.builder().set(DataComponents.MAX_STACK_SIZE, 1).build()); + try { + final Field components = Item.class.getDeclaredField("components"); + components.setAccessible(true); + components.set(spawner, updatedComponents); + } catch (@SuppressWarnings("unused") NoSuchFieldException | SecurityException | IllegalArgumentException + | IllegalAccessException e) { + try { + // DataComponentMap components -> c + final Field components = Item.class.getDeclaredField("c"); + components.setAccessible(true); + components.set(spawner.get().value(), updatedComponents); + } 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 = ResourceLocation.fromNamespaceAndPath("minecraft", safeMobID); + final Optional>> entityType = entityTypeRegistry.get(resourceLocation); + if (entityType.isEmpty()) { + return false; + } + tile.getSpawner().setEntityId(entityType.get().value(), 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); + final CustomData customData = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + final CompoundTag tag = blockData.copyTag(); + final CompoundTag customTag = customData.copyTag(); + + // Check for SilkSpawners key + if (!customTag.contains("SilkSpawners")) { + customTag.put("SilkSpawners", new CompoundTag()); + } + + customTag.getCompound("SilkSpawners").putString("entity", entity); + + // 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)); + itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(customTag)); + 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.CUSTOM_DATA, CustomData.EMPTY); + final CompoundTag tag = blockEntityData.copyTag(); + + if (!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); + final CompoundTag tag = blockEntityData.copyTag(); + + 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; + } + } + + @Override + public String getOtherPluginsNBTEntityID(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.ENTITY_DATA, CustomData.EMPTY); + final CompoundTag tag = blockEntityData.copyTag(); + + if (tag == null) { + return null; + } + if (tag.contains("ms_mob")) { + return tag.getString("ms_mob"); + } + 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 blockData = itemStack.getOrDefault(DataComponents.ENTITY_DATA, CustomData.EMPTY); + final CustomData customData = itemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY); + final CompoundTag tag = blockData.copyTag(); + final CompoundTag customTag = customData.copyTag(); + + if (!customTag.contains("SilkSpawners")) { + customTag.put("SilkSpawners", new CompoundTag()); + } + + customTag.getCompound("SilkSpawners").putString("entity", entityID); + + String prefixedEntity; + if (!entityID.startsWith("minecraft:")) { + prefixedEntity = "minecraft:" + entityID; + } else { + prefixedEntity = entityID; + } + tag.putString("id", prefixedEntity); + + itemStack.set(DataComponents.ENTITY_DATA, CustomData.of(tag)); + itemStack.set(DataComponents.CUSTOM_DATA, CustomData.of(customTag)); + 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.ENTITY_DATA, CustomData.EMPTY); + final CompoundTag tag = blockEntityData.copyTag(); + + if (tag.contains("id")) { + return tag.getString("id").replace("minecraft:", ""); + } + + final Registry itemRegistry = BuiltInRegistries.ITEM; + final ResourceLocation vanillaKey = itemRegistry.getKey(itemStack.getItem()); + if (vanillaKey != null) { + return vanillaKey.getPath().replace("minecraft:", "").replace("_spawn_egg", ""); + } + + 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 4082b8f6..14fbfc9e 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ modules/v1_20_R3 modules/v1_20_R4 modules/v1_21_R1 + modules/v1_21_R3 modules/SilkSpawners