Paper/CraftBukkit-Patches/0017-Entity-Activation-Range.patch

459 lines
21 KiB
Diff

From 652752f24fa214ce92cdb698689df4d5dc7b1e6c 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.
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
index 8a10948..f5203bc 100644
--- a/src/main/java/net/minecraft/server/Entity.java
+++ b/src/main/java/net/minecraft/server/Entity.java
@@ -88,7 +88,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;
@@ -111,8 +111,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;
@@ -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 f46c920..1699059 100644
--- a/src/main/java/net/minecraft/server/EntityArrow.java
+++ b/src/main/java/net/minecraft/server/EntityArrow.java
@@ -14,7 +14,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/World.java b/src/main/java/net/minecraft/server/World.java
index 51816e4..7e10318 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;
@@ -1275,6 +1276,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);
@@ -1435,8 +1437,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 77078a1..6a0f02c 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -103,9 +103,15 @@ 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
info = configuration.getBoolean("world-settings." + name + ".info", info);
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));
@@ -125,6 +131,10 @@ public class CraftWorld implements World {
obfuscated = world.getServer().orebfuscatorEnabled && !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);
+
if (!info) return;
server.getLogger().info("-------------- Spigot ----------------");
server.getLogger().info("-------- World Settings For [" + name + "] --------");
@@ -141,6 +151,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
}
@@ -161,6 +172,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 58361fa..61cd44d 100644
--- a/src/main/java/org/bukkit/craftbukkit/Spigot.java
+++ b/src/main/java/org/bukkit/craftbukkit/Spigot.java
@@ -1,12 +1,43 @@
package org.bukkit.craftbukkit;
import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import net.minecraft.server.AxisAlignedBB;
+import net.minecraft.server.Chunk;
+import net.minecraft.server.Entity;
+import net.minecraft.server.EntityAmbient;
+import net.minecraft.server.EntityAnimal;
+import net.minecraft.server.EntityArrow;
+import net.minecraft.server.EntityComplexPart;
+import net.minecraft.server.EntityCreature;
+import net.minecraft.server.EntityEnderCrystal;
+import net.minecraft.server.EntityEnderDragon;
+import net.minecraft.server.EntityFireball;
+import net.minecraft.server.EntityFireworks;
+import net.minecraft.server.EntityHuman;
+import net.minecraft.server.EntityItemFrame;
+import net.minecraft.server.EntityLiving;
+import net.minecraft.server.EntityMonster;
+import net.minecraft.server.EntityProjectile;
+import net.minecraft.server.EntitySheep;
+import net.minecraft.server.EntitySlime;
+import net.minecraft.server.EntityTNTPrimed;
+import net.minecraft.server.EntityWeather;
+import net.minecraft.server.EntityWither;
+import net.minecraft.server.MathHelper;
+import net.minecraft.server.MinecraftServer;
import net.minecraft.server.PendingConnection;
+import net.minecraft.server.World;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.configuration.file.YamlConfiguration;
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);
private static boolean filterIps;
public static void initialize(CraftServer server, SimpleCommandMap commandMap, YamlConfiguration configuration) {
@@ -25,12 +56,228 @@ public class Spigot {
server.orebfuscatorDisabledWorlds = configuration.getStringList("orebfuscator.disabled-worlds");
server.orebfuscatorBlocks = configuration.getShortList("orebfuscator.blocks");
if (server.orebfuscatorEngineMode != 1 && server.orebfuscatorEngineMode != 2) {
- server.orebfuscatorEngineMode = 1;
+ server.orebfuscatorEngineMode = 1;
}
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;
}
public static void filterIp(PendingConnection con) {
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 9f6c57d..655ee24 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
info: true
world:
growth-chunks-per-tick: 1000
--
1.8.2.1