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)
This commit is contained in:
pop4959 2020-08-05 12:47:42 -07:00 committed by GitHub
parent 68d0aa9688
commit 14c6c2a055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 15 deletions

View File

@ -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<String> 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

View File

@ -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);

View File

@ -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<User> getOnlineUsers();
SpawnerProvider getSpawnerProvider();
SpawnerItemProvider getSpawnerItemProvider();
SpawnerBlockProvider getSpawnerBlockProvider();
ServerStateProvider getServerStateProvider();
}

View File

@ -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) {

View File

@ -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);

View File

@ -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.");
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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();

View File

@ -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";
}
}

View File

@ -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;
}
}