From ebf5bbc06365e256b68f41e46a169b514e8bd8f0 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/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java index 59bfd325f..687701e3e 100644 --- a/src/main/java/net/minecraft/server/level/WorldServer.java +++ b/src/main/java/net/minecraft/server/level/WorldServer.java @@ -370,6 +370,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { gameprofilerfiller.pop(); } + org.spigotmc.ActivationRange.activateEntities(this); // Spigot timings.entityTick.startTiming(); // Spigot this.entityTickList.forEach((entity) -> { if (!entity.isRemoved()) { @@ -778,6 +779,13 @@ public class WorldServer extends World implements GeneratorAccessSeed { } public void tickNonPassenger(Entity entity) { + // Spigot start + if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { + entity.tickCount++; + entity.inactiveTick(); + return; + } + // Spigot end entity.tickTimer.startTiming(); // Spigot entity.setOldPosAndRot(); GameProfilerFiller gameprofilerfiller = this.getProfiler(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index e01cbf640..21228643d 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -288,6 +288,12 @@ public abstract class Entity implements INamableTileEntity, EntityAccess, IComma public boolean forceExplosionKnockback; // SPIGOT-949 public boolean persistentInvisibility = false; public CustomTimingsHandler tickTimer = org.bukkit.craftbukkit.SpigotTimings.getEntityTimings(this); // Spigot + // Spigot start + public final org.spigotmc.ActivationRange.ActivationType activationType = org.spigotmc.ActivationRange.initializeEntityActivationType(this); + public final boolean defaultActivationState; + public long activatedTick = Integer.MIN_VALUE; + public void inactiveTick() { } + // Spigot end public float getBukkitYaw() { return this.yRot; @@ -322,6 +328,13 @@ public abstract class Entity implements INamableTileEntity, EntityAccess, IComma this.blockPosition = BlockPosition.ZERO; this.chunkPosition = ChunkCoordIntPair.ZERO; this.packetCoordinates = Vec3D.ZERO; + // Spigot start + if (world != null) { + this.defaultActivationState = org.spigotmc.ActivationRange.initializeEntityActivationState(this, world.spigotConfig); + } else { + this.defaultActivationState = false; + } + // Spigot end this.entityData = new DataWatcher(this); this.entityData.define(Entity.DATA_SHARED_FLAGS_ID, (byte) 0); this.entityData.define(Entity.DATA_AIR_SUPPLY_ID, this.getMaxAirSupply()); diff --git a/src/main/java/net/minecraft/world/entity/EntityAgeable.java b/src/main/java/net/minecraft/world/entity/EntityAgeable.java index e441825f6..d22a2c2d4 100644 --- a/src/main/java/net/minecraft/world/entity/EntityAgeable.java +++ b/src/main/java/net/minecraft/world/entity/EntityAgeable.java @@ -25,6 +25,31 @@ public abstract class EntityAgeable extends EntityCreature { super(entitytypes, world); } + // Spigot start + @Override + public void inactiveTick() + { + super.inactiveTick(); + if ( this.level.isClientSide || this.ageLocked ) + { // CraftBukkit + this.refreshDimensions(); + } else + { + int i = this.getAge(); + + if ( i < 0 ) + { + ++i; + this.setAge( i ); + } else if ( i > 0 ) + { + --i; + this.setAge( i ); + } + } + } + // Spigot end + @Override public GroupDataEntity finalizeSpawn(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @Nullable GroupDataEntity groupdataentity, @Nullable NBTTagCompound nbttagcompound) { if (groupdataentity == null) { diff --git a/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java b/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java index fb29c994c..af7c159d9 100644 --- a/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java +++ b/src/main/java/net/minecraft/world/entity/EntityAreaEffectCloud.java @@ -182,6 +182,18 @@ public class EntityAreaEffectCloud extends Entity { this.duration = i; } + // Spigot start - copied from below + @Override + public void inactiveTick() { + super.inactiveTick(); + + if (this.tickCount >= this.waitTime + this.duration) { + this.discard(); + return; + } + } + // Spigot end + @Override public void tick() { super.tick(); diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java index 69b035f9f..8e6670abf 100644 --- a/src/main/java/net/minecraft/world/entity/EntityLiving.java +++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java @@ -265,6 +265,13 @@ public abstract class EntityLiving extends Entity { return getYHeadRot(); } // CraftBukkit end + // Spigot start + public void inactiveTick() + { + super.inactiveTick(); + ++this.noActionTime; // Above all the floats + } + // Spigot end protected EntityLiving(EntityTypes entitytypes, World world) { super(entitytypes, world); diff --git a/src/main/java/net/minecraft/world/entity/item/EntityItem.java b/src/main/java/net/minecraft/world/entity/item/EntityItem.java index 4d76a2946..a5ca65eab 100644 --- a/src/main/java/net/minecraft/world/entity/item/EntityItem.java +++ b/src/main/java/net/minecraft/world/entity/item/EntityItem.java @@ -185,6 +185,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.level.isClientSide && this.age >= level.spigotConfig.itemDespawnRate) { // Spigot + // CraftBukkit start - fire ItemDespawnEvent + if (org.bukkit.craftbukkit.event.CraftEventFactory.callItemDespawnEvent(this).isCancelled()) { + this.age = 0; + return; + } + // CraftBukkit end + this.discard(); + } + } + // Spigot end + private void setUnderwaterMovement() { Vec3D vec3d = this.getDeltaMovement(); diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java index 150ea7dcd..5fffd7ea0 100644 --- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java @@ -220,6 +220,17 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation return this.assignProfessionWhenSpawned; } + // Spigot Start + @Override + public void inactiveTick() { + // SPIGOT-3874, SPIGOT-3894, SPIGOT-3846, SPIGOT-5286 :( + if (level.spigotConfig.tickInactiveVillagers && this.isEffectiveAi()) { + this.customServerAiStep(); + } + super.inactiveTick(); + } + // Spigot End + @Override protected void customServerAiStep() { this.level.getProfiler().push("villagerBrain"); diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java index 32219948f..db1bd7c1d 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java +++ b/src/main/java/net/minecraft/world/entity/projectile/EntityArrow.java @@ -73,6 +73,18 @@ public abstract class EntityArrow extends IProjectile { @Nullable private List piercedAndKilledEntities; + // Spigot Start + @Override + public void inactiveTick() + { + if ( this.inGround ) + { + this.life += 1; + } + super.inactiveTick(); + } + // Spigot End + protected EntityArrow(EntityTypes entitytypes, World world) { super(entitytypes, world); this.pickup = EntityArrow.PickupStatus.DISALLOWED; diff --git a/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java b/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java index d9a2ec084..4851fc9a4 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java +++ b/src/main/java/net/minecraft/world/entity/projectile/EntityFireworks.java @@ -81,6 +81,22 @@ public class EntityFireworks extends IProjectile implements ItemSupplier { this.setOwner(entity); } + // Spigot Start - copied from tick + @Override + public void inactiveTick() { + this.life += 1; + + if (!this.level.isClientSide && this.life > this.lifetime) { + // CraftBukkit start + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) { + this.explode(); + } + // CraftBukkit end + } + super.inactiveTick(); + } + // Spigot End + @Override protected void defineSynchedData() { this.entityData.define(EntityFireworks.DATA_ID_FIREWORKS_ITEM, ItemStack.EMPTY); diff --git a/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java b/src/main/java/org/bukkit/craftbukkit/SpigotTimings.java index aff7b6b43..d3328a695 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 000000000..7e3ec868f --- /dev/null +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -0,0 +1,256 @@ +package org.spigotmc; + +import java.util.Collection; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.MathHelper; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityCreature; +import net.minecraft.world.entity.EntityLightning; +import net.minecraft.world.entity.EntityLiving; +import net.minecraft.world.entity.ambient.EntityAmbient; +import net.minecraft.world.entity.animal.EntityAnimal; +import net.minecraft.world.entity.animal.EntitySheep; +import net.minecraft.world.entity.boss.EntityComplexPart; +import net.minecraft.world.entity.boss.enderdragon.EntityEnderCrystal; +import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; +import net.minecraft.world.entity.boss.wither.EntityWither; +import net.minecraft.world.entity.item.EntityTNTPrimed; +import net.minecraft.world.entity.monster.EntityCreeper; +import net.minecraft.world.entity.monster.EntityMonster; +import net.minecraft.world.entity.monster.EntitySlime; +import net.minecraft.world.entity.npc.EntityVillager; +import net.minecraft.world.entity.player.EntityHuman; +import net.minecraft.world.entity.projectile.EntityArrow; +import net.minecraft.world.entity.projectile.EntityFireball; +import net.minecraft.world.entity.projectile.EntityFireworks; +import net.minecraft.world.entity.projectile.EntityProjectile; +import net.minecraft.world.entity.projectile.EntityThrownTrident; +import net.minecraft.world.entity.raid.EntityRaider; +import net.minecraft.world.level.World; +import net.minecraft.world.level.chunk.Chunk; +import net.minecraft.world.phys.AxisAlignedBB; +import org.bukkit.craftbukkit.SpigotTimings; + +public class ActivationRange +{ + + public enum ActivationType + { + MONSTER, + ANIMAL, + RAIDER, + MISC; + + AxisAlignedBB boundingBox = new AxisAlignedBB( 0, 0, 0, 0, 0, 0 ); + } + + static AxisAlignedBB maxBB = 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 ActivationType initializeEntityActivationType(Entity entity) + { + if ( entity instanceof EntityRaider ) + { + return ActivationType.RAIDER; + } else if ( entity instanceof EntityMonster || entity instanceof EntitySlime ) + { + return ActivationType.MONSTER; + } else if ( entity instanceof EntityCreature || entity instanceof EntityAmbient ) + { + return ActivationType.ANIMAL; + } else + { + return ActivationType.MISC; + } + } + + /** + * These entities are excluded from Activation range checks. + * + * @param entity + * @param config + * @return boolean If it should always tick. + */ + public static boolean initializeEntityActivationState(Entity entity, SpigotWorldConfig config) + { + if ( ( entity.activationType == ActivationType.MISC && config.miscActivationRange == 0 ) + || ( entity.activationType == ActivationType.RAIDER && config.raiderActivationRange == 0 ) + || ( entity.activationType == ActivationType.ANIMAL && config.animalActivationRange == 0 ) + || ( entity.activationType == ActivationType.MONSTER && config.monsterActivationRange == 0 ) + || entity instanceof EntityHuman + || entity instanceof EntityProjectile + || entity instanceof EntityEnderDragon + || entity instanceof EntityComplexPart + || entity instanceof EntityWither + || entity instanceof EntityFireball + || entity instanceof EntityLightning + || entity instanceof EntityTNTPrimed + || entity instanceof EntityEnderCrystal + || entity instanceof EntityFireworks + || entity instanceof EntityThrownTrident ) + { + 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 raiderActivationRange = world.spigotConfig.raiderActivationRange; + final int animalActivationRange = world.spigotConfig.animalActivationRange; + final int monsterActivationRange = world.spigotConfig.monsterActivationRange; + + int maxRange = Math.max( monsterActivationRange, animalActivationRange ); + maxRange = Math.max( maxRange, raiderActivationRange ); + maxRange = Math.max( maxRange, miscActivationRange ); + maxRange = Math.min( ( world.spigotConfig.simulationDistance << 4 ) - 8, maxRange ); + + for ( EntityHuman player : world.players() ) + { + + player.activatedTick = MinecraftServer.currentTick; + maxBB = player.getBoundingBox().inflate( maxRange, 256, maxRange ); + ActivationType.MISC.boundingBox = player.getBoundingBox().inflate( miscActivationRange, 256, miscActivationRange ); + ActivationType.RAIDER.boundingBox = player.getBoundingBox().inflate( raiderActivationRange, 256, raiderActivationRange ); + ActivationType.ANIMAL.boundingBox = player.getBoundingBox().inflate( animalActivationRange, 256, animalActivationRange ); + ActivationType.MONSTER.boundingBox = player.getBoundingBox().inflate( monsterActivationRange, 256, monsterActivationRange ); + + world.getEntities().get(maxBB, ActivationRange::activateEntity); + } + SpigotTimings.entityActivationCheckTimer.stopTiming(); + } + + /** + * Checks for the activation state of all entities in this chunk. + * + * @param chunk + */ + private static void activateEntity(Entity entity) + { + if ( MinecraftServer.currentTick > entity.activatedTick ) + { + if ( entity.defaultActivationState ) + { + entity.activatedTick = MinecraftServer.currentTick; + return; + } + if ( entity.activationType.boundingBox.intersects( 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.wasTouchingWater || entity.remainingFireTicks > 0 ) + { + return true; + } + if ( !( entity instanceof EntityArrow ) ) + { + if ( !entity.isOnGround() || !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.hurtTime > 0 || living.activeEffects.size() > 0 ) + { + return true; + } + if ( entity instanceof EntityCreature && ( (EntityCreature) entity ).getTarget() != null ) + { + return true; + } + if ( entity instanceof EntityVillager && ( (EntityVillager) entity ).canBreed() ) + { + 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 + if ( 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.tickCount % 4 == 0 && !checkEntityImmunities( entity ) ) + { + 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 9fc17f219..90b765bdf 100644 --- a/src/main/java/org/spigotmc/SpigotWorldConfig.java +++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java @@ -188,4 +188,19 @@ public class SpigotWorldConfig itemDespawnRate = getInt( "item-despawn-rate", 6000 ); log( "Item Despawn Rate: " + itemDespawnRate ); } + + public int animalActivationRange = 32; + public int monsterActivationRange = 32; + public int raiderActivationRange = 48; + public int miscActivationRange = 16; + public boolean tickInactiveVillagers = true; + private void activationRange() + { + animalActivationRange = getInt( "entity-activation-range.animals", animalActivationRange ); + monsterActivationRange = getInt( "entity-activation-range.monsters", monsterActivationRange ); + raiderActivationRange = getInt( "entity-activation-range.raiders", raiderActivationRange ); + miscActivationRange = getInt( "entity-activation-range.misc", miscActivationRange ); + tickInactiveVillagers = getBoolean( "entity-activation-range.tick-inactive-villagers", tickInactiveVillagers ); + log( "Entity Activation Range: An " + animalActivationRange + " / Mo " + monsterActivationRange + " / Ra " + raiderActivationRange + " / Mi " + miscActivationRange + " / Tiv " + tickInactiveVillagers ); + } } -- 2.25.1