From 14c6c2a055e8c123d63e3ee5140a5583f6744fab Mon Sep 17 00:00:00 2001 From: pop4959 Date: Wed, 5 Aug 2020 12:47:42 -0700 Subject: [PATCH] Fix spawner delay feature (#3239) Closes #1332 The spawner delay feature has been broken in Essentials for as long as anyone can remember. The reasons for this are mentioned in the issue above. This PR fixes this by changing the command to utilize new API for setting the minimum and maximum spawn delay on spawners. This API was added in 1.12.2, so all supported versions before that (1.8.8 thru 1.12.1) require NMS to function properly. I'm aware that Essentials avoids NMS for maintainability reasons, however that should not be of much concern here since all versions 1.12.2 and later are going to be using the Bukkit API. Hence, no NMS updates will be necessary. Also let me know if you want the NMS code refactored somewhere else. I saw the net.ess3.nms packages, but I wasn't sure where this would fit into the organisation of that. Tested on: 1.8.8, 1.9.4, 1.10.2, 1.11.2 (NMS) 1.12.2, 1.15.2 (Bukkit API) --- .../com/earth2me/essentials/Essentials.java | 27 +++++++-- .../essentials/EssentialsBlockListener.java | 2 +- .../com/earth2me/essentials/IEssentials.java | 7 ++- .../essentials/commands/Commandspawner.java | 8 +++ .../earth2me/essentials/items/FlatItemDb.java | 4 +- .../essentials/items/LegacyItemDb.java | 2 +- .../ess3/provider/SpawnerBlockProvider.java | 9 +++ ...Provider.java => SpawnerItemProvider.java} | 2 +- ...java => BlockMetaSpawnerItemProvider.java} | 4 +- .../providers/BukkitSpawnerBlockProvider.java | 21 +++++++ .../providers/ReflSpawnerBlockProvider.java | 55 +++++++++++++++++++ 11 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 providers/BaseProviders/src/net/ess3/provider/SpawnerBlockProvider.java rename providers/BaseProviders/src/net/ess3/provider/{SpawnerProvider.java => SpawnerItemProvider.java} (96%) rename providers/BaseProviders/src/net/ess3/provider/providers/{BlockMetaSpawnerProvider.java => BlockMetaSpawnerItemProvider.java} (89%) create mode 100644 providers/BaseProviders/src/net/ess3/provider/providers/BukkitSpawnerBlockProvider.java create mode 100644 providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflSpawnerBlockProvider.java diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index 47be9587a..379c034ba 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -40,11 +40,13 @@ import net.ess3.api.ISettings; import net.ess3.api.*; import net.ess3.nms.refl.providers.ReflServerStateProvider; import net.ess3.nms.refl.providers.ReflSpawnEggProvider; +import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider; import net.ess3.provider.PotionMetaProvider; import net.ess3.provider.ProviderListener; import net.ess3.provider.ServerStateProvider; import net.ess3.provider.SpawnEggProvider; -import net.ess3.provider.SpawnerProvider; +import net.ess3.provider.SpawnerBlockProvider; +import net.ess3.provider.SpawnerItemProvider; import net.ess3.provider.providers.*; import org.bukkit.Bukkit; import org.bukkit.Server; @@ -107,7 +109,8 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient EssentialsTimer timer; private final transient Set vanishedPlayers = new LinkedHashSet<>(); private transient Method oldGetOnlinePlayers; - private transient SpawnerProvider spawnerProvider; + private transient SpawnerItemProvider spawnerItemProvider; + private transient SpawnerBlockProvider spawnerBlockProvider; private transient SpawnEggProvider spawnEggProvider; private transient PotionMetaProvider potionMetaProvider; private transient ServerStateProvider serverStateProvider; @@ -244,8 +247,15 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { confList.add(jails); execTimer.mark("Init(Jails)"); - //Spawner provider only uses one but it's here for legacy... - spawnerProvider = new BlockMetaSpawnerProvider(); + //Spawner item provider only uses one but it's here for legacy... + spawnerItemProvider = new BlockMetaSpawnerItemProvider(); + + //Spawner block providers + if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_12_0_R01)) { + spawnerBlockProvider = new ReflSpawnerBlockProvider(); + } else { + spawnerBlockProvider = new BukkitSpawnerBlockProvider(); + } //Spawn Egg Providers if (VersionUtil.getServerBukkitVersion().isLowerThanOrEqualTo(VersionUtil.v1_8_8_R01)) { @@ -945,8 +955,13 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { } @Override - public SpawnerProvider getSpawnerProvider() { - return spawnerProvider; + public SpawnerItemProvider getSpawnerItemProvider() { + return spawnerItemProvider; + } + + @Override + public SpawnerBlockProvider getSpawnerBlockProvider() { + return spawnerBlockProvider; } @Override diff --git a/Essentials/src/com/earth2me/essentials/EssentialsBlockListener.java b/Essentials/src/com/earth2me/essentials/EssentialsBlockListener.java index 6d2c1024b..7d2c8ef38 100644 --- a/Essentials/src/com/earth2me/essentials/EssentialsBlockListener.java +++ b/Essentials/src/com/earth2me/essentials/EssentialsBlockListener.java @@ -37,7 +37,7 @@ public class EssentialsBlockListener implements Listener { final BlockState blockState = event.getBlockPlaced().getState(); if (blockState instanceof CreatureSpawner) { final CreatureSpawner spawner = (CreatureSpawner) blockState; - final EntityType type = ess.getSpawnerProvider().getEntityType(event.getItemInHand()); + final EntityType type = ess.getSpawnerItemProvider().getEntityType(event.getItemInHand()); if (type != null && Mob.fromBukkitType(type) != null) { if (ess.getUser(event.getPlayer()).isAuthorized("essentials.spawnerconvert." + Mob.fromBukkitType(type).name().toLowerCase(Locale.ENGLISH))) { spawner.setSpawnedType(type); diff --git a/Essentials/src/com/earth2me/essentials/IEssentials.java b/Essentials/src/com/earth2me/essentials/IEssentials.java index 3f4d8a6bc..4646f638d 100644 --- a/Essentials/src/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/com/earth2me/essentials/IEssentials.java @@ -6,7 +6,8 @@ import com.earth2me.essentials.api.IWarps; import com.earth2me.essentials.perm.PermissionsHandler; import com.earth2me.essentials.register.payment.Methods; import net.ess3.provider.ServerStateProvider; -import net.ess3.provider.SpawnerProvider; +import net.ess3.provider.SpawnerBlockProvider; +import net.ess3.provider.SpawnerItemProvider; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -103,7 +104,9 @@ public interface IEssentials extends Plugin { Iterable getOnlineUsers(); - SpawnerProvider getSpawnerProvider(); + SpawnerItemProvider getSpawnerItemProvider(); + + SpawnerBlockProvider getSpawnerBlockProvider(); ServerStateProvider getServerStateProvider(); } diff --git a/Essentials/src/com/earth2me/essentials/commands/Commandspawner.java b/Essentials/src/com/earth2me/essentials/commands/Commandspawner.java index ee079d678..a7590eeb9 100644 --- a/Essentials/src/com/earth2me/essentials/commands/Commandspawner.java +++ b/Essentials/src/com/earth2me/essentials/commands/Commandspawner.java @@ -7,6 +7,7 @@ import com.earth2me.essentials.utils.EnumUtil; import com.earth2me.essentials.utils.LocationUtil; import com.earth2me.essentials.utils.NumberUtil; import com.earth2me.essentials.utils.StringUtil; +import net.ess3.provider.SpawnerBlockProvider; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; @@ -59,6 +60,13 @@ public class Commandspawner extends EssentialsCommand { try { CreatureSpawner spawner = (CreatureSpawner) target.getBlock().getState(); spawner.setSpawnedType(mob.getType()); + if (delay > 0) { + SpawnerBlockProvider spawnerBlockProvider = ess.getSpawnerBlockProvider(); + spawnerBlockProvider.setMinSpawnDelay(spawner, 1); + spawnerBlockProvider.setMaxSpawnDelay(spawner, Integer.MAX_VALUE); + spawnerBlockProvider.setMinSpawnDelay(spawner, delay); + spawnerBlockProvider.setMaxSpawnDelay(spawner, delay); + } spawner.setDelay(delay); spawner.update(); } catch (Throwable ex) { diff --git a/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java b/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java index 202052956..e33c476c9 100644 --- a/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java +++ b/Essentials/src/com/earth2me/essentials/items/FlatItemDb.java @@ -145,7 +145,7 @@ public class FlatItemDb extends AbstractItemDb { // setItemMeta to prevent a race condition EntityType entity = data.getEntity(); if (entity != null && material.toString().contains("SPAWNER")) { - ess.getSpawnerProvider().setEntityType(stack, entity); + ess.getSpawnerItemProvider().setEntityType(stack, entity); } return stack; @@ -203,7 +203,7 @@ public class FlatItemDb extends AbstractItemDb { PotionData potion = ((PotionMeta) item.getItemMeta()).getBasePotionData(); return new ItemData(type, potion); } else if (type.toString().contains("SPAWNER")) { - EntityType entity = ess.getSpawnerProvider().getEntityType(item); + EntityType entity = ess.getSpawnerItemProvider().getEntityType(item); return new ItemData(type, entity); } else { return new ItemData(type); diff --git a/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java b/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java index ea9a47107..a7d3457f3 100644 --- a/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java +++ b/Essentials/src/com/earth2me/essentials/items/LegacyItemDb.java @@ -185,7 +185,7 @@ public class LegacyItemDb extends AbstractItemDb { if (mat == MOB_SPAWNER) { if (metaData == 0) metaData = EntityType.PIG.getTypeId(); try { - retval = ess.getSpawnerProvider().setEntityType(retval, EntityType.fromId(metaData)); + retval = ess.getSpawnerItemProvider().setEntityType(retval, EntityType.fromId(metaData)); } catch (IllegalArgumentException e) { throw new Exception("Can't spawn entity ID " + metaData + " from mob spawners."); } diff --git a/providers/BaseProviders/src/net/ess3/provider/SpawnerBlockProvider.java b/providers/BaseProviders/src/net/ess3/provider/SpawnerBlockProvider.java new file mode 100644 index 000000000..98f2f8b26 --- /dev/null +++ b/providers/BaseProviders/src/net/ess3/provider/SpawnerBlockProvider.java @@ -0,0 +1,9 @@ +package net.ess3.provider; + +import org.bukkit.block.CreatureSpawner; + +public interface SpawnerBlockProvider extends Provider { + void setMaxSpawnDelay(CreatureSpawner spawner, int delay); + + void setMinSpawnDelay(CreatureSpawner spawner, int delay); +} diff --git a/providers/BaseProviders/src/net/ess3/provider/SpawnerProvider.java b/providers/BaseProviders/src/net/ess3/provider/SpawnerItemProvider.java similarity index 96% rename from providers/BaseProviders/src/net/ess3/provider/SpawnerProvider.java rename to providers/BaseProviders/src/net/ess3/provider/SpawnerItemProvider.java index 4d479c810..d377ee8bd 100644 --- a/providers/BaseProviders/src/net/ess3/provider/SpawnerProvider.java +++ b/providers/BaseProviders/src/net/ess3/provider/SpawnerItemProvider.java @@ -9,7 +9,7 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -public interface SpawnerProvider extends Provider { +public interface SpawnerItemProvider extends Provider { ItemStack setEntityType(ItemStack is, EntityType type) throws IllegalArgumentException; EntityType getEntityType(ItemStack is) throws IllegalArgumentException; diff --git a/providers/BaseProviders/src/net/ess3/provider/providers/BlockMetaSpawnerProvider.java b/providers/BaseProviders/src/net/ess3/provider/providers/BlockMetaSpawnerItemProvider.java similarity index 89% rename from providers/BaseProviders/src/net/ess3/provider/providers/BlockMetaSpawnerProvider.java rename to providers/BaseProviders/src/net/ess3/provider/providers/BlockMetaSpawnerItemProvider.java index ac5c1bd28..cc67da3b2 100644 --- a/providers/BaseProviders/src/net/ess3/provider/providers/BlockMetaSpawnerProvider.java +++ b/providers/BaseProviders/src/net/ess3/provider/providers/BlockMetaSpawnerItemProvider.java @@ -1,13 +1,13 @@ package net.ess3.provider.providers; -import net.ess3.provider.SpawnerProvider; +import net.ess3.provider.SpawnerItemProvider; import org.bukkit.block.BlockState; import org.bukkit.block.CreatureSpawner; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BlockStateMeta; -public class BlockMetaSpawnerProvider implements SpawnerProvider { +public class BlockMetaSpawnerItemProvider implements SpawnerItemProvider { @Override public ItemStack setEntityType(ItemStack is, EntityType type) throws IllegalArgumentException { BlockStateMeta bsm = (BlockStateMeta) is.getItemMeta(); diff --git a/providers/BaseProviders/src/net/ess3/provider/providers/BukkitSpawnerBlockProvider.java b/providers/BaseProviders/src/net/ess3/provider/providers/BukkitSpawnerBlockProvider.java new file mode 100644 index 000000000..eac24e970 --- /dev/null +++ b/providers/BaseProviders/src/net/ess3/provider/providers/BukkitSpawnerBlockProvider.java @@ -0,0 +1,21 @@ +package net.ess3.provider.providers; + +import net.ess3.provider.SpawnerBlockProvider; +import org.bukkit.block.CreatureSpawner; + +public class BukkitSpawnerBlockProvider implements SpawnerBlockProvider { + @Override + public void setMaxSpawnDelay(CreatureSpawner spawner, int delay) { + spawner.setMaxSpawnDelay(delay); + } + + @Override + public void setMinSpawnDelay(CreatureSpawner spawner, int delay) { + spawner.setMinSpawnDelay(delay); + } + + @Override + public String getDescription() { + return "Bukkit 1.12+ provider"; + } +} diff --git a/providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflSpawnerBlockProvider.java b/providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflSpawnerBlockProvider.java new file mode 100644 index 000000000..554cc9908 --- /dev/null +++ b/providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflSpawnerBlockProvider.java @@ -0,0 +1,55 @@ +package net.ess3.nms.refl.providers; + +import net.ess3.nms.refl.ReflUtil; +import net.ess3.provider.SpawnerBlockProvider; +import org.bukkit.block.CreatureSpawner; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ReflSpawnerBlockProvider implements SpawnerBlockProvider { + @Override + public void setMaxSpawnDelay(CreatureSpawner spawner, int delay) { + Class mobSpawnerAbstract = ReflUtil.getNMSClass("MobSpawnerAbstract"); + Field maxSpawnDelay = ReflUtil.getFieldCached(mobSpawnerAbstract, "maxSpawnDelay"); + if (maxSpawnDelay != null) { + try { + maxSpawnDelay.setInt(getNMSSpawner(spawner), delay); + } catch (IllegalAccessException ignored) { + } + } + } + + @Override + public void setMinSpawnDelay(CreatureSpawner spawner, int delay) { + Class mobSpawnerAbstract = ReflUtil.getNMSClass("MobSpawnerAbstract"); + Field minSpawnDelay = ReflUtil.getFieldCached(mobSpawnerAbstract, "minSpawnDelay"); + if (minSpawnDelay != null) { + try { + minSpawnDelay.setInt(getNMSSpawner(spawner), delay); + } catch (IllegalAccessException ignored) { + } + } + } + + @Override + public String getDescription() { + return "Reflection based provider"; + } + + private Object getNMSSpawner(CreatureSpawner spawner) { + try { + Class craftWorld = ReflUtil.getOBCClass("CraftWorld"); + Class tileEntityMobSpawner = ReflUtil.getNMSClass("TileEntityMobSpawner"); + Method getSpawner = ReflUtil.getMethodCached(tileEntityMobSpawner, "getSpawner"); + Method getTileEntityAt = ReflUtil.getMethodCached(craftWorld, "getTileEntityAt", int.class, int.class, int.class); + if (getSpawner != null && getTileEntityAt != null) { + Object craftTileEntity = getTileEntityAt.invoke(spawner.getWorld(), spawner.getX(), spawner.getY(), spawner.getZ()); + return getSpawner.invoke(craftTileEntity); + } + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + return null; + } +}