From 279f8e96072730a4c8b597d7d763cf54d61b5629 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. diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 63cc105..1817e6d 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -107,7 +107,7 @@ public abstract class Entity implements ICommandListener { public int ticksLived; public int maxFireTicks; public int fireTicks; - public boolean inWater; + public boolean inWater; // Spigot - protected -> public // PAIL public int noDamageTicks; protected boolean justCreated; protected boolean fireProof; @@ -141,6 +141,12 @@ public abstract class Entity implements ICommandListener { public org.bukkit.projectiles.ProjectileSource projectileSource; // CraftBukkit - For projectiles only public boolean forceExplosionKnockback; // CraftBukkit - SPIGOT-949 public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot + // Spigot start + public final byte activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; + public void inactiveTick() { } + // Spigot end public Entity(World world) { this.id = Entity.entityCount++; @@ -160,7 +166,12 @@ public abstract class Entity implements ICommandListener { this.setPosition(0.0D, 0.0D, 0.0D); if (world != null) { this.dimension = world.worldProvider.getDimensionManager().getDimensionID(); + // Spigot start + this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig); + } else { + this.defaultActivationState = false; } + // Spigot end this.datawatcher = new DataWatcher(this); this.datawatcher.register(Entity.ax, Byte.valueOf((byte) 0)); diff --git a/src/main/java/net/minecraft/server/EntityAgeable.java b/src/main/java/net/minecraft/server/EntityAgeable.java index e4a02bc..dbede68 100644 --- a/src/main/java/net/minecraft/server/EntityAgeable.java +++ b/src/main/java/net/minecraft/server/EntityAgeable.java @@ -10,6 +10,31 @@ public abstract class EntityAgeable extends EntityCreature { private float bx; public boolean ageLocked; // CraftBukkit + // Spigot start + @Override + public void inactiveTick() + { + super.inactiveTick(); + if ( this.world.isClientSide || this.ageLocked ) + { // CraftBukkit + this.a( this.isBaby() ); + } else + { + int i = this.getAge(); + + if ( i < 0 ) + { + ++i; + this.setAgeRaw( i ); + } else if ( i > 0 ) + { + --i; + this.setAgeRaw( i ); + } + } + } + // Spigot end + public EntityAgeable(World world) { super(world); } diff --git a/src/main/java/net/minecraft/server/EntityArrow.java b/src/main/java/net/minecraft/server/EntityArrow.java index 1cda4e6..a40f3ec 100644 --- a/src/main/java/net/minecraft/server/EntityArrow.java +++ b/src/main/java/net/minecraft/server/EntityArrow.java @@ -27,7 +27,7 @@ public abstract class EntityArrow extends Entity implements IProjectile { private int at; private Block au; private int av; - protected boolean inGround; + public boolean inGround; // Spigot - protected -> public protected int b; public EntityArrow.PickupStatus fromPlayer; public int shake; @@ -37,6 +37,18 @@ public abstract class EntityArrow extends Entity implements IProjectile { private double damage; public int knockbackStrength; + // Spigot Start + @Override + public void inactiveTick() + { + if ( this.inGround ) + { + this.aw += 1; // Despawn counter. First int after shooter + } + super.inactiveTick(); + } + // Spigot End + public EntityArrow(World world) { super(world); this.h = -1; diff --git a/src/main/java/net/minecraft/server/EntityFireworks.java b/src/main/java/net/minecraft/server/EntityFireworks.java index 7454fde..ff44aa4 100644 --- a/src/main/java/net/minecraft/server/EntityFireworks.java +++ b/src/main/java/net/minecraft/server/EntityFireworks.java @@ -13,6 +13,14 @@ public class EntityFireworks extends Entity { this.setSize(0.25F, 0.25F); } + // Spigot Start + @Override + public void inactiveTick() { + this.ticksFlown += 1; + super.inactiveTick(); + } + // Spigot End + protected void i() { this.datawatcher.register(EntityFireworks.FIREWORK_ITEM, Optional.absent()); } diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java index eb0f8eb..39af5d5 100644 --- a/src/main/java/net/minecraft/server/EntityItem.java +++ b/src/main/java/net/minecraft/server/EntityItem.java @@ -122,6 +122,28 @@ public class EntityItem extends Entity { } } + // Spigot start - copied from above + @Override + public void inactiveTick() { + // CraftBukkit start - Use wall time for pickup and despawn timers + int elapsedTicks = MinecraftServer.currentTick - this.lastTick; + if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks; + if (this.age != -32768) this.age += elapsedTicks; + this.lastTick = MinecraftServer.currentTick; + // CraftBukkit end + + if (!this.world.isClientSide && this.age >= world.spigotConfig.itemDespawnRate) { // Spigot + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; + return; + } + // CraftBukkit end + this.die(); + } + } + // Spigot end + private void x() { // Spigot start double radius = world.spigotConfig.itemMerge; diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java index dc91f0a..36ef280 100644 --- a/src/main/java/net/minecraft/server/EntityLiving.java +++ b/src/main/java/net/minecraft/server/EntityLiving.java @@ -102,6 +102,13 @@ public abstract class EntityLiving extends Entity { ArrayList drops; public org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; // CraftBukkit end + // Spigot start + public void inactiveTick() + { + super.inactiveTick(); + ++this.ticksFarFromPlayer; // Above all the floats + } + // Spigot end public void Q() { this.damageEntity(DamageSource.OUT_OF_WORLD, Float.MAX_VALUE); diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java index 718ad04..a6bd744 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -1310,6 +1310,7 @@ public abstract class World implements IBlockAccess { CrashReportSystemDetails crashreportsystemdetails1; CrashReport crashreport1; + org.spigotmc.ActivationRange.activateEntities(this); // Spigot timings.entityTick.startTiming(); // Spigot // CraftBukkit start - Use field for loop variable for (this.tickPosition = 0; this.tickPosition < this.entityList.size(); ++this.tickPosition) { @@ -1487,9 +1488,11 @@ public abstract class World implements IBlockAccess { int j = MathHelper.floor(entity.locZ); byte b0 = 32; - // CraftBukkit start - Use neighbor cache instead of looking up - Chunk startingChunk = this.getChunkIfLoaded(i >> 4, j >> 4); - if (!flag || (startingChunk != null && startingChunk.areNeighborsLoaded(2)) /* this.isAreaLoaded(i - b0, 0, j - b0, i + b0, 0, j + b0) */) { + // Spigot start + if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { + entity.ticksLived++; + entity.inactiveTick(); + } else { entity.tickTimer.startTiming(); // Spigot // CraftBukkit end entity.M = entity.locX; diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java index 558574f..41d2d87 100644 --- a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java +++ b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java @@ -39,6 +39,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/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java new file mode 100644 index 0000000..c4364cd --- /dev/null +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -0,0 +1,286 @@ +package org.spigotmc; + +import java.util.Set; +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.EntityCreeper; +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.EntityLiving; +import net.minecraft.server.EntityMonster; +import net.minecraft.server.EntityProjectile; +import net.minecraft.server.EntitySheep; +import net.minecraft.server.EntitySlice; +import net.minecraft.server.EntitySlime; +import net.minecraft.server.EntityTNTPrimed; +import net.minecraft.server.EntityVillager; +import net.minecraft.server.EntityWeather; +import net.minecraft.server.EntityWither; +import net.minecraft.server.MathHelper; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.World; +import org.bukkit.craftbukkit.SpigotTimings; + +public class ActivationRange +{ + + static AxisAlignedBB maxBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB miscBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB animalBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + static AxisAlignedBB monsterBB = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + + /** + * 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, SpigotWorldConfig config) + { + if ( ( entity.activationType == 3 && config.miscActivationRange == 0 ) + || ( entity.activationType == 2 && config.animalActivationRange == 0 ) + || ( entity.activationType == 1 && config.monsterActivationRange == 0 ) + || entity instanceof EntityHuman + || 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; + } + + /** + * 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.spigotConfig.miscActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); + maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); + + for ( EntityHuman player : world.players ) + { + + player.activatedTick = MinecraftServer.currentTick; + maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange ); + miscBB = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange ); + animalBB = player.getBoundingBox().grow( animalActivationRange, 256, animalActivationRange ); + monsterBB = player.getBoundingBox().grow( 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 ( EntitySlice slice : chunk.entitySlices ) + { + for ( Entity entity : (Set) slice ) + { + if ( MinecraftServer.currentTick > entity.activatedTick ) + { + if ( entity.defaultActivationState ) + { + entity.activatedTick = MinecraftServer.currentTick; + continue; + } + switch ( entity.activationType ) + { + case 1: + if ( monsterBB.b( entity.getBoundingBox() ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + break; + case 2: + if ( animalBB.b( entity.getBoundingBox() ) ) + { + entity.activatedTick = MinecraftServer.currentTick; + } + break; + case 3: + default: + if ( miscBB.b( entity.getBoundingBox() ) ) + { + 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 || entity.fireTicks > 0 ) + { + return true; + } + if ( !( entity instanceof EntityArrow ) ) + { + if ( !entity.onGround || !entity.passengers.isEmpty() || entity.isPassenger() ) + { + return true; + } + } else if ( !( (EntityArrow) entity ).inGround ) + { + return true; + } + // special cases. + if ( entity instanceof EntityLiving ) + { + EntityLiving living = (EntityLiving) entity; + if ( /*TODO: Missed mapping? living.attackTicks > 0 || */ living.hurtTicks > 0 || living.effects.size() > 0 ) + { + return true; + } + if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).getGoalTarget() != null ) + { + return true; + } + if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).da() /* Getter for first boolean */ ) + { + return true; + } + if ( entity instanceof EntityAnimal ) + { + EntityAnimal animal = (EntityAnimal) entity; + if ( animal.isBaby() || animal.isInLove() ) + { + return true; + } + if ( entity instanceof EntitySheep && ( (EntitySheep) entity ).isSheared() ) + { + return true; + } + } + if (entity instanceof EntityCreeper && ((EntityCreeper) entity).isIgnited()) { // isExplosive + 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(); + // Never safe to skip fireworks or entities not yet added to chunk + // PAIL: inChunk + if ( !entity.aa || entity instanceof EntityFireworks ) { + SpigotTimings.checkIfActiveTimer.stopTiming(); + return true; + } + + 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 + Chunk chunk = entity.world.getChunkIfLoaded( x >> 4, z >> 4 ); + if ( isActive && !( chunk != null && chunk.areNeighborsLoaded( 1 ) ) ) + { + isActive = false; + } + SpigotTimings.checkIfActiveTimer.stopTiming(); + return isActive; + } +} diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java index ca888fc..92dbe54 100644 --- a/src/main/java/org/spigotmc/SpigotWorldConfig.java +++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java @@ -136,4 +136,15 @@ public class SpigotWorldConfig itemDespawnRate = getInt( "item-despawn-rate", 6000 ); log( "Item Despawn Rate: " + itemDespawnRate ); } + + public int animalActivationRange = 32; + public int monsterActivationRange = 32; + public int miscActivationRange = 16; + private void activationRange() + { + animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange ); + monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange ); + miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange ); + log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Mi " + miscActivationRange ); + } } -- 2.5.0