first commit

This commit is contained in:
Esmorall 2019-09-02 20:07:33 -03:00
commit 90b5d50841
14 changed files with 739 additions and 0 deletions

View File

@ -0,0 +1,29 @@
#----------------------------------
# EntityTrackerFixer
#
# By: Esmorall
#
#----------------------------------
#Log when the plugin untrack entities
log-to-console: true
#How many ticks between untrack process
#The untrack process will check for untracked entities by players but still by the server, and untrack them.
untrack-ticks: 1000
#if tps are not below this value, the task will not perform the untrack and it will wait for the next run
tps-limit: 18.5
#frecuency in ticks to check untracked entities
#It will check for players traking entities and will track it again (this is to prevent invisible entities)
check-untracked-entities-frequency: 60
#Distance in blocks to check for players near untracked entities
tracking-range: 25
#which worlds do you want the plugin to take effect?
worlds:
- world
- world_nether
- world_the_end

View File

@ -0,0 +1,6 @@
name: EntityTrackerFixer
main: net.minemora.entitytrackerfixer.EntityTrackerFixer
version: 1.0.8
api-version: 1.14
author: Esmorall
commands:

View File

@ -0,0 +1,104 @@
package net.minemora.entitytrackerfixer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_14_R1.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import net.minecraft.server.v1_14_R1.ChunkProviderServer;
import net.minecraft.server.v1_14_R1.WorldServer;
import net.minemora.entitytrackerfixer.config.ConfigMain;
import net.minemora.entitytrackerfixer.util.Util;
public class CheckTask extends BukkitRunnable {
@Override
public void run() {
if(UntrackerTask.isRunning()) {
return;
}
for(String worldName : ConfigMain.getWorlds()) {
if(Bukkit.getWorld(worldName) == null) {
continue;
}
if(UntrackedEntitiesCache.getInstance().isEmpty(worldName)) {
continue;
}
checkWorld(worldName);
}
}
public void checkWorld(String worldName) {
WorldServer ws = ((CraftWorld)Bukkit.getWorld(worldName)).getHandle();
ChunkProviderServer cps = ws.getChunkProvider();
Set<UntrackedEntity> toRemove = new HashSet<>();
Set<net.minecraft.server.v1_14_R1.Entity> trackAgain = new HashSet<>();
Iterator<UntrackedEntity> it = UntrackedEntitiesCache.getInstance().getCache(worldName).iterator();
while (it.hasNext()) {
UntrackedEntity ute = it.next();
net.minecraft.server.v1_14_R1.Entity nmsEnt = ute.getEntity();
if(cps.playerChunkMap.trackedEntities.containsKey(nmsEnt.getId())) {
//System.out.println("removed (et contains): " + nmsEnt.getBukkitEntity().getType().name());
toRemove.add(ute);
continue;
}
World world = nmsEnt.getBukkitEntity().getWorld();
Location loc = nmsEnt.getBukkitEntity().getLocation();
if(!Util.isChunkLoaded(ws, loc.getBlockX() >> 4, loc.getBlockZ() >> 4)) {
//System.out.println("removed (unloaded chunk x:"+(loc.getBlockX() >> 4)+" z:"+(loc.getBlockZ() >> 4)+"): " + nmsEnt.getBukkitEntity().getType().name());
UntrackedEntitiesCache.getInstance().addUFC(worldName, nmsEnt.getId());
toRemove.add(ute);
continue;
}
if(!worldName.equals(world.getName())) {
//System.out.println("removed (different world): " + nmsEnt.getBukkitEntity().getType().name());
toRemove.add(ute);
continue;
}
if(nmsEnt.getBukkitEntity().isDead()) {
//System.out.println("removed (is dead): " + nmsEnt.getBukkitEntity().getType().name());
toRemove.add(ute);
continue;
}
boolean track = false;
int d = ConfigMain.getTrackingRange();
List<Entity> ents = nmsEnt.getBukkitEntity().getNearbyEntities(d, d, d);
if(ents.isEmpty()) {
continue;
}
for(Entity le : ents) {
if(le == null) {
continue;
}
if(le instanceof Player) {
track = true;
break;
}
}
if(track) {
//System.out.println("tracked again: " + nmsEnt.getBukkitEntity().getType().name());
trackAgain.add(nmsEnt);
}
}
UntrackedEntitiesCache.getInstance().removeAll(toRemove, worldName);
new BukkitRunnable() {
@Override
public void run() {
NMSEntityTracker.trackEntities(cps, trackAgain);
}
}.runTask(EntityTrackerFixer.plugin);
}
}

View File

@ -0,0 +1,20 @@
package net.minemora.entitytrackerfixer;
import org.bukkit.plugin.java.JavaPlugin;
import net.minemora.entitytrackerfixer.config.ConfigMain;
import net.minemora.entitytrackerfixer.listener.ChunkEventListener;
public class EntityTrackerFixer extends JavaPlugin {
public static EntityTrackerFixer plugin;
@Override
public void onEnable() {
plugin = this;
ConfigMain.getInstance().setup(this);
new UntrackerTask().runTaskTimerAsynchronously(this, ConfigMain.getUntrackTicks(), ConfigMain.getUntrackTicks());
new CheckTask().runTaskTimerAsynchronously(this, ConfigMain.getUntrackTicks() + 1, ConfigMain.getCheckFrequency());
getServer().getPluginManager().registerEvents(new ChunkEventListener(), this);
}
}

View File

@ -0,0 +1,43 @@
package net.minemora.entitytrackerfixer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import net.minecraft.server.v1_14_R1.ChunkProviderServer;
import net.minecraft.server.v1_14_R1.PlayerChunkMap;
import net.minemora.entitytrackerfixer.util.ReflectionUtils;
public final class NMSEntityTracker {
private NMSEntityTracker() {}
public static void trackEntities(ChunkProviderServer cps, Set<net.minecraft.server.v1_14_R1.Entity> trackList) {
try {
Method method = ReflectionUtils.getPrivateMethod(PlayerChunkMap.class, "addEntity",
new Class[] {net.minecraft.server.v1_14_R1.Entity.class});
for(net.minecraft.server.v1_14_R1.Entity entity : trackList) {
if(cps.playerChunkMap.trackedEntities.containsKey(entity.getId())) {
continue;
}
method.invoke(cps.playerChunkMap, entity);
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
public static void untrackEntities(ChunkProviderServer cps, Set<net.minecraft.server.v1_14_R1.Entity> untrackList) {
try {
Method method = ReflectionUtils.getPrivateMethod(PlayerChunkMap.class, "removeEntity",
new Class[] {net.minecraft.server.v1_14_R1.Entity.class});
for(net.minecraft.server.v1_14_R1.Entity entity : untrackList) {
method.invoke(cps.playerChunkMap, entity);
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,56 @@
package net.minemora.entitytrackerfixer;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class TrackedWorld {
private final String worldName;
private Set<UntrackedEntity> cache = ConcurrentHashMap.newKeySet();
private Set<Integer> unloadedFromChunkCache = ConcurrentHashMap.newKeySet();
public TrackedWorld(String worldName) {
this.worldName = worldName;
}
public void add(net.minecraft.server.v1_14_R1.Entity entity) {
cache.add(new UntrackedEntity(entity));
}
public void remove(UntrackedEntity ute) {
cache.remove(ute);
}
public void removeAll(Set<UntrackedEntity> toRemove) {
cache.removeAll(toRemove);
}
public boolean isEmpty(String worldName) {
return cache.isEmpty();
}
public Set<UntrackedEntity> getCache() {
return cache;
}
public void addUFC(int i) {
unloadedFromChunkCache.add(i);
}
public void removeUFC(int i) {
unloadedFromChunkCache.remove(i);
}
public boolean containsUFC(int i) {
return unloadedFromChunkCache.contains(i);
}
public Set<Integer> getUnloadedFromChunkCache() {
return unloadedFromChunkCache;
}
public String getWorldName() {
return worldName;
}
}

View File

@ -0,0 +1,104 @@
package net.minemora.entitytrackerfixer;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.minemora.entitytrackerfixer.config.ConfigMain;
public final class UntrackedEntitiesCache {
private static UntrackedEntitiesCache instance;
private Map<String,TrackedWorld> trackedWorlds = new ConcurrentHashMap<>();
private UntrackedEntitiesCache() {
for(String worldName : ConfigMain.getWorlds()) {
trackedWorlds.put(worldName, new TrackedWorld(worldName));
}
}
public void add(net.minecraft.server.v1_14_R1.Entity entity) {
String worldName = entity.getBukkitEntity().getWorld().getName();
if(!trackedWorlds.containsKey(worldName)) {
return;
}
TrackedWorld tw = trackedWorlds.get(worldName);
tw.add(entity);
}
public void remove(UntrackedEntity ute) {
String worldName = ute.getEntity().getBukkitEntity().getWorld().getName();
if(!trackedWorlds.containsKey(worldName)) {
return;
}
TrackedWorld tw = trackedWorlds.get(worldName);
tw.remove(ute);
}
public void removeAll(Set<UntrackedEntity> toRemove, String worldName) {
if(!trackedWorlds.containsKey(worldName)) {
return;
}
TrackedWorld tw = trackedWorlds.get(worldName);
tw.removeAll(toRemove);
}
public boolean isEmpty(String worldName) {
if(!trackedWorlds.containsKey(worldName)) {
return true;
}
return getCache(worldName).isEmpty();
}
public Set<UntrackedEntity> getCache(String worldName) {
if(!trackedWorlds.containsKey(worldName)) {
return new HashSet<UntrackedEntity>();
}
TrackedWorld tw = trackedWorlds.get(worldName);
return tw.getCache();
}
public void addUFC(String worldName, int i) {
if(!trackedWorlds.containsKey(worldName)) {
return;
}
TrackedWorld tw = trackedWorlds.get(worldName);
tw.addUFC(i);
}
public void removeUFC(String worldName, int i) {
if(!trackedWorlds.containsKey(worldName)) {
return;
}
TrackedWorld tw = trackedWorlds.get(worldName);
tw.removeUFC(i);
}
public boolean containsUFC(String worldName, int i) {
if(!trackedWorlds.containsKey(worldName)) {
return true;
}
return getUnloadedFromChunkCache(worldName).contains(i);
}
public Set<Integer> getUnloadedFromChunkCache(String worldName) {
if(!trackedWorlds.containsKey(worldName)) {
return new HashSet<Integer>();
}
TrackedWorld tw = trackedWorlds.get(worldName);
return tw.getUnloadedFromChunkCache();
}
public static UntrackedEntitiesCache getInstance() {
if(instance == null) {
instance = new UntrackedEntitiesCache();
}
return instance;
}
public Map<String,TrackedWorld> getTrackedWorlds() {
return trackedWorlds;
}
}

View File

@ -0,0 +1,40 @@
package net.minemora.entitytrackerfixer;
public class UntrackedEntity {
private final net.minecraft.server.v1_14_R1.Entity entity;
private final int id;
public UntrackedEntity(net.minecraft.server.v1_14_R1.Entity entity) {
this.entity = entity;
this.id = entity.getId();
}
public net.minecraft.server.v1_14_R1.Entity getEntity() {
return entity;
}
public int getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if ((o instanceof UntrackedEntity) && (((UntrackedEntity) o).getId() == this.id)) {
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
return id;
}
}

View File

@ -0,0 +1,111 @@
package net.minemora.entitytrackerfixer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.ObjectIterator;
import org.bukkit.craftbukkit.v1_14_R1.CraftWorld;
import org.bukkit.scheduler.BukkitRunnable;
import net.minecraft.server.v1_14_R1.ChunkProviderServer;
import net.minecraft.server.v1_14_R1.EntityPlayer;
import net.minecraft.server.v1_14_R1.MinecraftServer;
import net.minecraft.server.v1_14_R1.WorldServer;
import net.minecraft.server.v1_14_R1.PlayerChunkMap.EntityTracker;
import net.minemora.entitytrackerfixer.config.ConfigMain;
import net.minemora.entitytrackerfixer.util.ReflectionUtils;
import net.minemora.entitytrackerfixer.util.Util;
public class UntrackerTask extends BukkitRunnable {
private static boolean running = false;
@SuppressWarnings("deprecation")
@Override
public void run() {
if(MinecraftServer.getServer().recentTps[0] > ConfigMain.getMinTps()) {
//String tps = String.format("%.2f", MinecraftServer.getServer().recentTps[0]);
//EntityTrackerFixer.plugin.getLogger().info("Not untraking because tps = " + tps);
return;
}
running = true;
for(String worldName : ConfigMain.getWorlds()) {
untrackProcess(worldName);
}
running = false;
}
private void untrackProcess(String worldName) {
if(Bukkit.getWorld(worldName) == null) {
return;
}
//Set<net.minecraft.server.v1_14_R1.Entity> toRemove = new HashSet<>();
int removed = 0;
WorldServer ws = ((CraftWorld)Bukkit.getWorld(worldName)).getHandle();
ChunkProviderServer cps = ws.getChunkProvider();
@SuppressWarnings("rawtypes")
ObjectIterator objectiterator = cps.playerChunkMap.trackedEntities.values().iterator();
try {
while (objectiterator.hasNext()) {
Object iterobj = objectiterator.next();
if(iterobj == null) {
objectiterator.remove();
continue;
}
EntityTracker et = (EntityTracker) iterobj;
net.minecraft.server.v1_14_R1.Entity nmsEnt = (net.minecraft.server.v1_14_R1.Entity)
ReflectionUtils.getPrivateField(et.getClass(), et, "tracker");
if(nmsEnt instanceof EntityPlayer) {
continue;
}
if(nmsEnt.getBukkitEntity().getCustomName() != null) {
continue;
}
boolean remove = false;
if(et.trackedPlayers.size() == 0) {
remove = true;
}
else if(et.trackedPlayers.size() == 1) {
for(EntityPlayer ep : et.trackedPlayers) {
if(!ep.getBukkitEntity().isOnline()) {
remove = true;
}
}
if(!remove) {
continue;
}
}
Location loc = nmsEnt.getBukkitEntity().getLocation();
if(!Util.isChunkLoaded(ws, loc.getBlockX() >> 4, loc.getBlockZ() >> 4)) {
UntrackedEntitiesCache.getInstance().addUFC(worldName, nmsEnt.getId());
}
if(remove) {
//System.out.println("untracked: " + nmsEnt.getBukkitEntity().getType().name());
//toRemove.add(nmsEnt);
objectiterator.remove();
removed++;
UntrackedEntitiesCache.getInstance().add(nmsEnt);
}
}
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
/*
new BukkitRunnable() {
@Override
public void run() {
NMSEntityTracker.untrackEntities(cps, toRemove);
}
}.runTask(EntityTrackerFixer.plugin);
*/
if(ConfigMain.isLogToConsole()) {
EntityTrackerFixer.plugin.getLogger().info("Untracked " + removed + " entities in " + worldName);
}
}
public static boolean isRunning() {
return running;
}
}

View File

@ -0,0 +1,73 @@
package net.minemora.entitytrackerfixer.config;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import com.google.common.io.ByteStreams;
public abstract class Config {
protected File pdfile;
protected FileConfiguration config;
protected String fileName;
protected Config(String fileName) {
this.fileName = fileName;
}
public void setup(Plugin plugin) {
if (!plugin.getDataFolder().exists()) {
plugin.getDataFolder().mkdir();
}
pdfile = new File(plugin.getDataFolder(), fileName);
boolean firstCreate = false;
if (!pdfile.exists()) {
firstCreate = true;
try {
pdfile.createNewFile();
try (InputStream is = plugin.getResource(fileName);
OutputStream os = new FileOutputStream(pdfile)) {
ByteStreams.copy(is, os);
}
} catch (IOException e) {
throw new RuntimeException("Unable to create the file: " + fileName, e);
}
}
config = YamlConfiguration.loadConfiguration(pdfile);
load(firstCreate);
update();
}
public abstract void load(boolean firstCreate);
public abstract void update();
public void save() {
try {
config.save(pdfile);
} catch (IOException e) {
Bukkit.getServer().getLogger().severe("Could not save " + fileName + "!");
}
}
public void reload() {
config = YamlConfiguration.loadConfiguration(pdfile);
}
public FileConfiguration getConfig() {
return config;
}
public String getFileName() {
return fileName;
}
}

View File

@ -0,0 +1,72 @@
package net.minemora.entitytrackerfixer.config;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.configuration.file.FileConfiguration;
public final class ConfigMain extends Config {
private static ConfigMain instance;
private static int untrackTicks;
private static int checkFrequency;
private static int trackingRange;
private static double minTps;
private static boolean logToConsole = true;
private static List<String> worlds = new ArrayList<>();
private ConfigMain() {
super("config.yml");
}
@Override
public void load(boolean firstCreate) {
untrackTicks = getConfig().getInt("untrack-ticks", 1000);
checkFrequency = getConfig().getInt("check-untracked-entities-frequency", 60);
trackingRange = getConfig().getInt("tracking-range", 25);
minTps = getConfig().getDouble("tps-limit", 18.5);
worlds = getConfig().getStringList("worlds");
logToConsole = getConfig().getBoolean("log-to-console", true);
}
public static FileConfiguration get() {
return getInstance().config;
}
public static ConfigMain getInstance() {
if (instance == null) {
instance = new ConfigMain();
}
return instance;
}
@Override
public void update() {
return;
}
public static int getUntrackTicks() {
return untrackTicks;
}
public static int getCheckFrequency() {
return checkFrequency;
}
public static double getMinTps() {
return minTps;
}
public static List<String> getWorlds() {
return worlds;
}
public static int getTrackingRange() {
return trackingRange;
}
public static boolean isLogToConsole() {
return logToConsole;
}
}

View File

@ -0,0 +1,28 @@
package net.minemora.entitytrackerfixer.listener;
import org.bukkit.Chunk;
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftEntity;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import net.minemora.entitytrackerfixer.UntrackedEntitiesCache;
import net.minemora.entitytrackerfixer.config.ConfigMain;
public class ChunkEventListener implements Listener {
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Chunk ch = event.getChunk();
if(!ConfigMain.getWorlds().contains(ch.getWorld().getName())) {
return;
}
for(Entity entity : ch.getEntities()) {
if(UntrackedEntitiesCache.getInstance().containsUFC(ch.getWorld().getName(), entity.getEntityId())) {
UntrackedEntitiesCache.getInstance().add(((CraftEntity)entity).getHandle());
UntrackedEntitiesCache.getInstance().removeUFC(ch.getWorld().getName(), entity.getEntityId());
}
}
}
}

View File

@ -0,0 +1,39 @@
package net.minemora.entitytrackerfixer.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public final class ReflectionUtils {
private ReflectionUtils() {}
public static Object getPrivateField(Class<? extends Object> clazz, Object obj, String fieldName)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Object ret = field.get(obj);
field.setAccessible(false);
return ret;
}
public static Object invokePrivateMethod(Class<? extends Object> clazz, Object obj, String methodName)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return invokePrivateMethod(clazz, obj, methodName, new Class[0]);
}
public static Object invokePrivateMethod(Class<? extends Object> clazz, Object obj, String methodName,
@SuppressWarnings("rawtypes") Class[] params, Object... args)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method method = getPrivateMethod(clazz, methodName, params);
return method.invoke(obj, args);
}
public static Method getPrivateMethod(Class<? extends Object> clazz, String methodName, @SuppressWarnings("rawtypes") Class[] params)
throws NoSuchMethodException, SecurityException {
Method method = clazz.getDeclaredMethod(methodName, params);
method.setAccessible(true);
return method;
}
}

View File

@ -0,0 +1,14 @@
package net.minemora.entitytrackerfixer.util;
import net.minecraft.server.v1_14_R1.WorldServer;
public final class Util {
private Util() {}
public static boolean isChunkLoaded(WorldServer ws, int x, int z) {
net.minecraft.server.v1_14_R1.Chunk chunk = ws.getChunkProvider().getChunkAt(x, z, false);
return chunk != null;
}
}