package me.ryanhamshire.PopulationDensity;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Monster;
import org.bukkit.entity.Rabbit;
import org.bukkit.entity.Tameable;
import org.bukkit.material.Colorable;
public class MonitorPerformanceTask implements Runnable
{
static Long lastExecutionTimestamp = null;
//runs every 1200 ticks (about 1 minute)
@Override
public void run()
{
if(lastExecutionTimestamp == null)
{
lastExecutionTimestamp = System.currentTimeMillis();
return;
}
long now = System.currentTimeMillis();
long millisecondsSinceLastExecution = now - lastExecutionTimestamp;
float tps = 1200 / (millisecondsSinceLastExecution / 1000f);
treatLag(tps);
lastExecutionTimestamp = now;
}
public static void treatLag(float tps)
{
boolean stopGrinders = false;
boolean bootIdlePlayers = false;
boolean removeEntities = false;
if(tps > 19)
{
//if we were lagging but aren't anymore, stop collecting performance data
if(PopulationDensity.minutesLagging > 5 && PopulationDensity.instance.config_captureSpigotTimingsWhenLagging && PopulationDensity.instance.isSpigotServer)
{
Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), "timings off");
}
PopulationDensity.minutesLagging = 0;
}
else
{
PopulationDensity.minutesLagging++;
if(PopulationDensity.instance.config_bootIdlePlayersWhenLagging)
{
bootIdlePlayers = true;
}
if(PopulationDensity.instance.config_disableGrindersWhenLagging)
{
stopGrinders = true;
}
if(PopulationDensity.instance.config_captureSpigotTimingsWhenLagging && PopulationDensity.instance.isSpigotServer)
{
//if lagging at least 5 minutes, start collecting performance data
if(PopulationDensity.minutesLagging == 5)
{
Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), "timings on");
}
//if lagging for 10 minutes, generate a performance report
else if(PopulationDensity.minutesLagging == 10)
{
Bukkit.getServer().dispatchCommand(Bukkit.getServer().getConsoleSender(), "timings paste");
}
}
if(tps <= 16 || PopulationDensity.minutesLagging >= 5)
{
removeEntities = true;
}
}
PopulationDensity.bootingIdlePlayersForLag = bootIdlePlayers;
PopulationDensity.grindersStopped = stopGrinders;
if(removeEntities && PopulationDensity.instance.thinAnimalAndMonsterCrowds)
{
thinEntities();
}
PopulationDensity.serverTicksPerSecond = tps;
}
@SuppressWarnings("deprecation")
static void thinEntities()
{
//thinnable entity types
final HashSet<EntityType> thinnableAnimals = new HashSet<EntityType>(Arrays.asList
(
EntityType.COW,
EntityType.HORSE,
EntityType.CHICKEN,
EntityType.SHEEP,
EntityType.PIG,
EntityType.WOLF,
EntityType.OCELOT,
EntityType.RABBIT,
EntityType.MUSHROOM_COW
));
int totalEntities = 0;
int totalRemoved = 0;
HashMap<String, Integer> totalEntityCounter = new HashMap<String, Integer>();
for(World world : PopulationDensity.instance.getServer().getWorlds())
{
Environment environment = world.getEnvironment();
HashSet<Material> allowedSpawnSurfaces = EntityEventHandler.allowedSpawnBlocks.get(environment);
for(Chunk chunk : world.getLoadedChunks())
{
HashMap<String, Integer> chunkEntityCounter = new HashMap<String, Integer>();
Entity [] entities = chunk.getEntities();
int monsterCount = 0;
boolean removedAnimalThisPass = false;
for(Entity entity : entities)
{
EntityType entityType = entity.getType();
String entityTypeID = entity.getType().name();
if(entityType == EntityType.SHEEP)
{
Colorable colorable = (Colorable)entity;
entityTypeID += colorable.getColor().name();
}
else if(entityType == EntityType.RABBIT)
{
Rabbit rabbit = (Rabbit)entity;
entityTypeID += rabbit.getRabbitType().name();
}
if(entity instanceof LivingEntity) totalEntities++;
//skip any pets
if(entity instanceof Tameable)
{
if(((Tameable)entity).isTamed()) continue;
}
//skip any entities with nameplates
if(entity.getCustomName() != null && entity.getCustomName() != "") continue;
//only specific types of animals may be removed
boolean isAnimal = entity instanceof Animals;
EntityType type = entity.getType();
if(isAnimal && !thinnableAnimals.contains(type)) continue;
Integer count = chunkEntityCounter.get(entityTypeID);
if(count == null) count = 0;
chunkEntityCounter.put(entityTypeID, count + 1);
if(type == EntityType.EXPERIENCE_ORB)
{
if(count > 15)
{
entity.remove();
totalRemoved++;
}
}
else if(type == EntityType.DROPPED_ITEM)
{
if(count > 25)
{
entity.remove();
totalRemoved++;
}
}
else if(type == EntityType.BOAT)
{
if(count > 5)
{
entity.remove();
totalRemoved++;
}
}
else if(entity instanceof Monster)
{
if(++monsterCount > 2)
{
entity.remove();
totalRemoved++;
}
else
{
Material underType = entity.getLocation().getBlock().getRelative(BlockFace.DOWN).getType();
if(!allowedSpawnSurfaces.contains(underType))
{
entity.remove();
totalRemoved++;
}
}
}
else if(isAnimal)
{
if(count > 20 || (count > 5 && !removedAnimalThisPass) || entity.getLocation().getBlock().getLightFromBlocks() < 4)
{
((Animals) entity).setHealth(0);
removedAnimalThisPass = true;
totalRemoved++;
if(PopulationDensity.instance.markRemovedEntityLocations)
{
Block block = entity.getLocation().getBlock();
Material blockType = block.getType();
if(blockType == Material.LONG_GRASS || blockType == Material.AIR)
{
block.setTypeIdAndData(31, (byte)0, false); //dead bush
}
}
}
}
else if(type == EntityType.PIG_ZOMBIE && entity.getWorld().getEnvironment() != Environment.NETHER)
{
entity.remove();
totalRemoved++;
}
}
for(String key : chunkEntityCounter.keySet())
{
Integer totalCounted = totalEntityCounter.get(key);
if(totalCounted == null) totalCounted = 0;
Integer chunkCounted = chunkEntityCounter.get(key);
totalCounted += chunkCounted;
totalEntityCounter.put(key, totalCounted);
}
}
}
PopulationDensity.AddLogEntry("Removed " + totalRemoved + " of " + totalEntities + " entities.");
if(PopulationDensity.minutesLagging > 5 && PopulationDensity.minutesLagging % 6 == 0)
{
PopulationDensity.AddLogEntry("Still lagging after thinning entities. Remaining entities by chunk and type:");
PopulationDensity.AddLogEntry("world;chunkx;chunkz;type;count");
for(World world : Bukkit.getServer().getWorlds())
{
for(Chunk chunk : world.getLoadedChunks())
{
BlockState [] blocks = chunk.getTileEntities();
int total = 0;
ConcurrentHashMap<String, Integer> entityCounter = new ConcurrentHashMap<String, Integer>();
for(BlockState block : blocks)
{
String typeName = block.getType().name();
Integer oldValue = entityCounter.get(typeName);
if(oldValue == null) oldValue = 0;
entityCounter.put(typeName, oldValue + 1);
total++;
}
Entity[] entities = chunk.getEntities();
for(Entity entity : entities)
{
String typeName = entity.getType().name();
Integer oldValue = entityCounter.get(typeName);
if(oldValue == null) oldValue = 0;
entityCounter.put(typeName, oldValue + 1);
total++;
}
for(String typeName : entityCounter.keySet())
{
PopulationDensity.AddLogEntry(";" + world.getName() + ";" + chunk.getX() + ";" + chunk.getZ() + ";" + typeName + ";" + entityCounter.get(typeName));
}
if(total > 10)
{
PopulationDensity.AddLogEntry(";" + world.getName() + ";" + chunk.getX() + ";" + chunk.getZ() + ";TOTAL;" + total);
}
}
}
}
}
}