From ec5887e4dec30708a0c515ce5a2e70cde34b7532 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 3 Feb 2013 05:10:21 -0500
Subject: [PATCH] Entity Activation Range

This feature gives 3 new configurable ranges that if an entity of the matching type is outside of this radius of any player, will tick at 5% of its normal rate.

This will drastically cut down on tick timings for entities that are not in range of a user to actually be "used".
This change can have dramatic impact on gameplay if configured too low. Balance according to your servers desired gameplay.
---
 src/main/java/net/minecraft/server/Entity.java     |  13 +-
 .../java/net/minecraft/server/EntityArrow.java     |   2 +-
 src/main/java/net/minecraft/server/EntityItem.java |   5 +-
 src/main/java/net/minecraft/server/World.java      |  10 +-
 .../java/org/bukkit/craftbukkit/CraftWorld.java    |  15 ++
 src/main/java/org/bukkit/craftbukkit/Spigot.java   | 219 +++++++++++++++++++++
 .../java/org/bukkit/craftbukkit/SpigotTimings.java |   3 +
 src/main/resources/configurations/bukkit.yml       |   3 +
 8 files changed, 264 insertions(+), 6 deletions(-)

diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index ccc4858..24ab35f 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -89,7 +89,7 @@ public abstract class Entity {
     public int ticksLived;
     public int maxFireTicks;
     public int fireTicks; // CraftBukkit - private -> public
-    protected boolean inWater;
+    public boolean inWater; // Spigot - protected -> public
     public int noDamageTicks;
     private boolean justCreated;
     protected boolean fireProof;
@@ -112,8 +112,14 @@ public abstract class Entity {
     public EnumEntitySize at;
     public boolean valid = false; // CraftBukkit
 
+    // Spigot start
     public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot
 
+    public final byte activationType = org.bukkit.craftbukkit.Spigot.initializeEntityActivationType(this);
+    public final boolean defaultActivationState;
+    public long activatedTick = 0;
+    // Spigot end
+
     public Entity(World world) {
         this.id = entityCount++;
         this.l = 1.0D;
@@ -154,7 +160,12 @@ public abstract class Entity {
         this.setPosition(0.0D, 0.0D, 0.0D);
         if (world != null) {
             this.dimension = world.worldProvider.dimension;
+            // Spigot start
+            this.defaultActivationState = org.bukkit.craftbukkit.Spigot.initializeEntityActivationState(this, world.getWorld());
+        } else {
+            this.defaultActivationState = false;
         }
+        // Spigot end
 
         this.datawatcher.a(0, Byte.valueOf((byte) 0));
         this.datawatcher.a(1, Short.valueOf((short) 300));
diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java
index 4b47364..647b91f 100644
--- a/src/main/java/net/minecraft/server/EntityArrow.java
+++ b/src/main/java/net/minecraft/server/EntityArrow.java
@@ -16,7 +16,7 @@ public class EntityArrow extends Entity implements IProjectile {
     private int f = -1;
     private int g = 0;
     private int h = 0;
-    private boolean inGround = false;
+    public boolean inGround = false; // Spigot - private -> public
     public int fromPlayer = 0;
     public int shake = 0;
     public Entity shooter;
diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java
index 6d54e97..924b6ed 100644
--- a/src/main/java/net/minecraft/server/EntityItem.java
+++ b/src/main/java/net/minecraft/server/EntityItem.java
@@ -100,8 +100,9 @@ public class EntityItem extends Entity {
         if (this.onGround) {
             this.motY *= -0.5D;
         }
-        } // Spigot
-        ++this.age;
+        }
+        this.age = ticksLived;
+        // Spigot
         if (!this.world.isStatic && this.age >= 6000) {
             // CraftBukkit start
             if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 09c3720..ca6d3f0 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -13,6 +13,7 @@ import java.util.concurrent.Callable;
 // CraftBukkit start
 import org.bukkit.Bukkit;
 import org.bukkit.craftbukkit.util.LongHashSet;
+import org.bukkit.craftbukkit.Spigot; // Spigot
 import org.bukkit.craftbukkit.SpigotTimings; // Spigot
 import org.bukkit.craftbukkit.util.UnsafeList;
 import org.bukkit.generator.ChunkGenerator;
@@ -1274,6 +1275,7 @@ public abstract class World implements IBlockAccess {
         this.f.clear();
         this.methodProfiler.c("regular");
 
+        org.bukkit.craftbukkit.Spigot.activateEntities(this); // Spigot
         timings.entityTick.startTiming(); // Spigot
         for (i = 0; i < this.entityList.size(); ++i) {
             entity = (Entity) this.entityList.get(i);
@@ -1434,8 +1436,12 @@ public abstract class World implements IBlockAccess {
         int j = MathHelper.floor(entity.locZ);
         byte b0 = 32;
 
-        if (!flag || this.e(i - b0, 0, j - b0, i + b0, 0, j + b0)) {
-            entity.tickTimer.startTiming(); // Spigot
+        // Spigot start
+        if (!Spigot.checkIfActive(entity)) {
+            entity.ticksLived++;
+        } else {
+            entity.tickTimer.startTiming();
+            // Spigot end
             entity.U = entity.locX;
             entity.V = entity.locY;
             entity.W = entity.locZ;
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index fbab028..80762c1 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -102,8 +102,14 @@ public class CraftWorld implements World {
         treeGrowthModifier = configuration.getInt("world-settings.default.tree-growth-modifier", treeGrowthModifier);
         mushroomGrowthModifier = configuration.getInt("world-settings.default.mushroom-growth-modifier", mushroomGrowthModifier);
 
+        miscEntityActivationRange = configuration.getInt("world-settings.default.entity-activation-range-misc");
+        animalEntityActivationRange = configuration.getInt("world-settings.default.entity-activation-range-animals");
+        monsterEntityActivationRange = configuration.getInt("world-settings.default.entity-activation-range-monsters");
+
         //override defaults with world specific, if they exist
         growthPerTick = configuration.getInt("world-settings." + name + ".growth-chunks-per-tick", growthPerTick);
+        itemMergeRadius = configuration.getDouble("world-settings." + name + ".item-merge-radius", itemMergeRadius);
+        expMergeRadius = configuration.getDouble("world-settings." + name + ".exp-merge-radius", expMergeRadius);
         randomLightingUpdates = configuration.getBoolean("world-settings." + name + ".random-light-updates", randomLightingUpdates);
         mobSpawnRange = configuration.getInt("world-settings." + name + ".mob-spawn-range", mobSpawnRange);
         aggregateTicks = Math.max(1, configuration.getInt("world-settings." + name + ".aggregate-chunkticks", aggregateTicks));
@@ -123,6 +129,10 @@ public class CraftWorld implements World {
 
         obfuscated = !world.getServer().orebfuscatorDisabledWorlds.contains(name);
 
+        miscEntityActivationRange = configuration.getInt("world-settings." + name + ".entity-activation-range-misc", miscEntityActivationRange);
+        animalEntityActivationRange = configuration.getInt("world-settings." + name + ".entity-activation-range-animals", animalEntityActivationRange);
+        monsterEntityActivationRange = configuration.getInt("world-settings." + name + ".entity-activation-range-monsters", monsterEntityActivationRange);
+
         server.getLogger().info("-------------- Spigot ----------------");
         server.getLogger().info("-------- World Settings For [" + name + "] --------");
         server.getLogger().info("Growth Per Chunk: " + growthPerTick);
@@ -138,6 +148,7 @@ public class CraftWorld implements World {
         server.getLogger().info("Mushroom Growth Modifier: " + mushroomGrowthModifier);
         server.getLogger().info("View distance: " + viewDistance);
         server.getLogger().info("Oreobfuscator: " + obfuscated);
+        server.getLogger().info("Entity Activation Range: An " + animalEntityActivationRange + " / Mo " + monsterEntityActivationRange + " / Mi " + miscEntityActivationRange);
         server.getLogger().info("-------------------------------------------------");
         // Spigot end
     }
@@ -158,6 +169,10 @@ public class CraftWorld implements World {
     public int sugarGrowthModifier = 100;
     public int treeGrowthModifier = 100;
     public int mushroomGrowthModifier = 100;
+
+    public int miscEntityActivationRange = 16;
+    public int animalEntityActivationRange = 32;
+    public int monsterEntityActivationRange = 32;
     // Spigot end
 
     public Block getBlockAt(int x, int y, int z) {
diff --git a/src/main/java/org/bukkit/craftbukkit/Spigot.java b/src/main/java/org/bukkit/craftbukkit/Spigot.java
index 4097568..705daef 100644
--- a/src/main/java/org/bukkit/craftbukkit/Spigot.java
+++ b/src/main/java/org/bukkit/craftbukkit/Spigot.java
@@ -1,9 +1,17 @@
 package org.bukkit.craftbukkit;
 
+import java.util.ArrayList;
+import net.minecraft.server.*;
 import org.bukkit.command.SimpleCommandMap;
 import org.bukkit.configuration.file.YamlConfiguration;
 
+import java.util.List;
+
 public class Spigot {
+    static AxisAlignedBB maxBB = AxisAlignedBB.a(0,0,0,0,0,0);
+    static AxisAlignedBB miscBB = AxisAlignedBB.a(0,0,0,0,0,0);
+    static AxisAlignedBB animalBB = AxisAlignedBB.a(0,0,0,0,0,0);
+    static AxisAlignedBB monsterBB = AxisAlignedBB.a(0,0,0,0,0,0);
 
     public static void initialize(CraftServer server, SimpleCommandMap commandMap, YamlConfiguration configuration) {
         commandMap.register("bukkit", new org.bukkit.craftbukkit.command.TicksPerSecondCommand("tps"));
@@ -27,5 +35,216 @@ public class Spigot {
         if (server.chunkGCPeriod == 0) {
             server.getLogger().severe("[Spigot] You should not disable chunk-gc, unexpected behaviour may occur!");
         }
+
+    }
+
+    /**
+     * Initializes an entities type on construction to specify what group this
+     * entity is in for activation ranges.
+     * @param entity
+     * @return group id
+     */
+    public static byte initializeEntityActivationType(Entity entity) {
+        if (entity instanceof EntityMonster || entity instanceof EntitySlime) {
+            return 1; // Monster
+        } else if (entity instanceof EntityCreature || entity instanceof EntityAmbient) {
+            return 2; // Animal
+        } else {
+            return 3; // Misc
+        }
+    }
+
+    /**
+     * These entities are excluded from Activation range checks.
+     *
+     * @param entity
+     * @param world
+     * @return boolean If it should always tick.
+     */
+    public static boolean initializeEntityActivationState(Entity entity, CraftWorld world) {
+        if (   (entity.activationType == 3 && world.miscEntityActivationRange == 0)
+            || (entity.activationType == 2 && world.animalEntityActivationRange == 0)
+            || (entity.activationType == 1 && world.monsterEntityActivationRange == 0)
+            || entity instanceof EntityHuman
+            || entity instanceof EntityItemFrame
+            || entity instanceof EntityProjectile
+            || entity instanceof EntityEnderDragon
+            || entity instanceof EntityComplexPart
+            || entity instanceof EntityWither
+            || entity instanceof EntityFireball
+            || entity instanceof EntityWeather
+            || entity instanceof EntityTNTPrimed
+            || entity instanceof EntityEnderCrystal
+            || entity instanceof EntityFireworks
+            ) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Utility method to grow an AABB without creating a new AABB or touching
+     * the pool, so we can re-use ones we have.
+     * @param target
+     * @param source
+     * @param x
+     * @param y
+     * @param z
+     */
+    public static void growBB(AxisAlignedBB target, AxisAlignedBB source, int x, int y, int z) {
+        target.a = source.a - x;
+        target.b = source.b - y;
+        target.c = source.c - z;
+        target.d = source.d + x;
+        target.e = source.e + y;
+        target.f = source.f + z;
+    }
+
+    /**
+     * Find what entities are in range of the players in the world and set
+     * active if in range.
+     * @param world
+     */
+    public static void activateEntities(World world) {
+        SpigotTimings.entityActivationCheckTimer.startTiming();
+        final int miscActivationRange = world.getWorld().miscEntityActivationRange;
+        final int animalActivationRange = world.getWorld().animalEntityActivationRange;
+        final int monsterActivationRange = world.getWorld().monsterEntityActivationRange;
+
+        int maxRange = Math.max(monsterActivationRange, animalActivationRange);
+        maxRange = Math.max(maxRange, miscActivationRange);
+        maxRange = Math.min((world.getWorld().viewDistance << 4) - 8, maxRange);
+
+        for (Entity player : new ArrayList<Entity>(world.players)) {
+
+            player.activatedTick = MinecraftServer.currentTick;
+            growBB(maxBB, player.boundingBox, maxRange, 256, maxRange);
+            growBB(miscBB, player.boundingBox, miscActivationRange, 256, miscActivationRange);
+            growBB(animalBB, player.boundingBox, animalActivationRange, 256, animalActivationRange);
+            growBB(monsterBB, player.boundingBox, monsterActivationRange, 256, monsterActivationRange);
+
+            int i = MathHelper.floor(maxBB.a / 16.0D);
+            int j = MathHelper.floor(maxBB.d / 16.0D);
+            int k = MathHelper.floor(maxBB.c / 16.0D);
+            int l = MathHelper.floor(maxBB.f / 16.0D);
+
+            for (int i1 = i; i1 <= j; ++i1) {
+                for (int j1 = k; j1 <= l; ++j1) {
+                    if (world.getWorld().isChunkLoaded(i1, j1)) {
+                        activateChunkEntities(world.getChunkAt(i1, j1));
+                    }
+                }
+            }
+        }
+        SpigotTimings.entityActivationCheckTimer.stopTiming();
+    }
+
+    /**
+     * Checks for the activation state of all entities in this chunk.
+     * @param chunk
+     */
+    private static void activateChunkEntities(Chunk chunk) {
+        for (List<Entity> slice : chunk.entitySlices) {
+            for (Entity entity : slice) {
+                if (MinecraftServer.currentTick > entity.activatedTick) {
+                    if (entity.defaultActivationState) {
+                        entity.activatedTick = MinecraftServer.currentTick;
+                        continue;
+                    }
+                    switch (entity.activationType) {
+                        case 1:
+                            if (monsterBB.a(entity.boundingBox)) {
+                                entity.activatedTick = MinecraftServer.currentTick;
+                            }
+                            break;
+                        case 2:
+                            if (animalBB.a(entity.boundingBox)) {
+                                entity.activatedTick = MinecraftServer.currentTick;
+                            }
+                            break;
+                        case 3:
+                        default:
+                            if (miscBB.a(entity.boundingBox)) {
+                                entity.activatedTick = MinecraftServer.currentTick;
+                            }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * If an entity is not in range, do some more checks to see if we should
+     * give it a shot.
+     * @param entity
+     * @return
+     */
+    public static boolean checkEntityImmunities(Entity entity) {
+        // quick checks.
+        if (entity.inWater /* isInWater */ || entity.fireTicks > 0) {
+            return true;
+        }
+        if (!(entity instanceof EntityArrow)) {
+            if (!entity.onGround || entity.passenger != null
+                || entity.vehicle != null) {
+                return true;
+            }
+        } else if (!((EntityArrow) entity).inGround) {
+            return true;
+        }
+        // special cases.
+        if (entity instanceof EntityLiving) {
+            EntityLiving living = (EntityLiving) entity;
+            if (living.attackTicks > 0 || living.hurtTicks > 0 || living.effects.size() > 0) {
+                return true;
+            }
+            if (entity instanceof EntityCreature && ((EntityCreature) entity).target != null) {
+                return true;
+            }
+            if (entity instanceof EntityAnimal) {
+                EntityAnimal animal = (EntityAnimal) entity;
+                if (animal.isBaby() || animal.r() /*love*/) {
+                    return true;
+                }
+                if (entity instanceof EntitySheep && ((EntitySheep) entity).isSheared()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks if the entity is active for this tick.
+     * @param entity
+     * @return
+     */
+    public static boolean checkIfActive(Entity entity) {
+        SpigotTimings.checkIfActiveTimer.startTiming();
+        boolean isActive = entity.activatedTick >= MinecraftServer.currentTick || entity.defaultActivationState;
+
+        // Should this entity tick?
+        if (!isActive) {
+            if ((MinecraftServer.currentTick - entity.activatedTick - 1) % 20 == 0) {
+                // Check immunities every 20 ticks.
+                if (checkEntityImmunities(entity)) {
+                    // Triggered some sort of immunity, give 20 full ticks before we check again.
+                    entity.activatedTick = MinecraftServer.currentTick + 20;
+                }
+                isActive = true;
+            }
+            // Add a little performance juice to active entities. Skip 1/4 if not immune.
+        } else if (!entity.defaultActivationState && entity.ticksLived % 4 == 0 && !checkEntityImmunities(entity)) {
+            isActive = false;
+        }
+        int x = MathHelper.floor(entity.locX);
+        int z = MathHelper.floor(entity.locZ);
+        // Make sure not on edge of unloaded chunk
+        if (isActive && !entity.world.areChunksLoaded(x, 0, z, 16)) {
+            isActive = false;
+        }
+        SpigotTimings.checkIfActiveTimer.stopTiming();
+        return isActive;
     }
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
index bbb6368..d8f73ad 100644
--- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
+++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java
@@ -29,6 +29,9 @@ public class SpigotTimings {
 
     public static final CustomTimingsHandler playerCommandTimer = new CustomTimingsHandler("** playerCommand");
 
+    public static final CustomTimingsHandler entityActivationCheckTimer = new CustomTimingsHandler("entityActivationCheck");
+    public static final CustomTimingsHandler checkIfActiveTimer = new CustomTimingsHandler("** checkIfActive");
+
     public static final HashMap<String, CustomTimingsHandler> entityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
     public static final HashMap<String, CustomTimingsHandler> tileEntityTypeTimingMap = new HashMap<String, CustomTimingsHandler>();
     public static final HashMap<String, CustomTimingsHandler> pluginTaskTimingMap = new HashMap<String, CustomTimingsHandler>();
diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml
index f0d731f..b741e37 100644
--- a/src/main/resources/configurations/bukkit.yml
+++ b/src/main/resources/configurations/bukkit.yml
@@ -46,6 +46,9 @@ world-settings:
         sugar-growth-modifier: 100
         tree-growth-modifier: 100
         mushroom-growth-modifier: 100
+        entity-activation-range-animals: 32
+        entity-activation-range-monsters: 32
+        entity-activation-range-misc: 16
     world:
         growth-chunks-per-tick: 1000
     world_nether:
-- 
1.8.1-rc2