From 8a22e56519a1b35e512bbb7f80070bcb9e67e9c9 Mon Sep 17 00:00:00 2001 From: Aikar 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 | 14 +- .../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, 263 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index bf9108a..8cbe086 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 ad; + public boolean ad; // Spigot - private -> public isInWater - If this renames, update Spigot.checkEntityImmunities public int noDamageTicks; private boolean justCreated; protected boolean fireProof; @@ -112,8 +112,14 @@ public abstract class Entity { public UUID uniqueId = UUID.randomUUID(); // CraftBukkit 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; @@ -153,7 +159,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 916b9dc..bdd18f6 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 5e3ac84..fdfd763 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 4fc1233..7d2bad3 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; @@ -1240,6 +1241,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); @@ -1406,12 +1408,12 @@ public abstract class World implements IBlockAccess { } public void entityJoinedWorld(Entity entity, boolean flag) { - int i = MathHelper.floor(entity.locX); - int j = MathHelper.floor(entity.locZ); - byte b0 = 32; - - if (!flag || this.d(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.T = entity.locX; entity.U = entity.locY; entity.V = entity.locZ; diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 21bd64a..33df602 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -100,10 +100,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); + 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)); @@ -121,6 +125,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 +146,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 +167,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 ad65bca..79be52d 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")); @@ -26,5 +34,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(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 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.ad /* 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 dec3110..c4e7dda 100644 --- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java +++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java @@ -30,6 +30,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 entityTypeTimingMap = new HashMap(); public static final HashMap tileEntityTypeTimingMap = new HashMap(); public static final HashMap pluginTaskTimingMap = new HashMap(); diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index 78e9a66..e568bf6 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