ecoCreature

ecoCreature

81.5k Downloads

CampByDistance Radius Calculation

javacraft opened this issue ยท 2 comments

commented

While reviewing the latest updates to ecoCreature, I came to notice your radius calculation method EntityUtil.nearSpawner() that determines if a mob or player is within range of a spawner. This method only examines tile entities within the chunk of the killed mob. This effectively limits the spawn radius to a minimum of 8 blocks (if spawner is in the middle of the chunk) and a maximum of 16 blocks (if spawner is in one corner and mob is killed in opposing corner).

I verified this by marking the boundaries of a chunk containing a spawner and setting SpawnRadius to 32. Any mob killed within the chunk was properly flagged as a camped critter. While any mob killed outside the spawner's chunk was not.

To properly check whether the spawn distance rule is valid, you'd have to examine a 2Nx2N chunk area around the mob/player, where N = SpawnRadius/16. Obviously, this could lead to some serious calculation overhead if SpawnRadius is large, since iterating over chunk tiles is expensive. It might be best to consider another use of Bukkit Metadata API to maintain the spawn location of the mob. Building upon the earlier snippet:

public class EntityListener implements Listener {
    public static final String SPAWN_TAG_MDID = "ecoCreature.spawned";
    public static final String SPAWN_LOC_MDID = "ecoCreature.spawnLoc";

    private FixedMetadataValue spawnerTag;
    private JavaPlugin plugin;

    public EntityListener( JavaPlugin plugin ) {
        this.plugin = plugin;
        this.spawnerTag = new FixedMetadataValue( plugin, true );
    }

    @EventHandler( priority = EventPriority.MONITOR )
    public void onCreatureSpawn( CreatureSpawnEvent evt ) {
        if ( evt.isCancelled() )
            return;

        if ( evt.getSpawnReason() == SpawnReason.SPAWNER ) {
            evt.getEntity().setMetadata( SPAWN_TAG_MDID, spawnerTag );
            evt.getEntity().setMetadata( SPAWN_LOC_MDID,
                    new FixedMetadataValue( plugin, evt.getLocation() ) );
            /*
             * If there is the possibility that other plugins may change the entity's
             * location use evt.getLocation().clone() instead to make a copy of the
             * current location.
             */
        }
    }
}

Given this, you can create a very simple and fast radius check, such as:

    public boolean isNearSpawner( Player player, Entity entity, int radius ) {
        int r2 = radius * radius;
        List<MetadataValue> mdList = entity.getMetadata( SPAWN_LOC_MDID );
        if ( mdList.size() == 0 )
            return false;

        // Assume this plugin is the only one with this metadata key. If not, need to
        // check MetadataValue.getOwningPlugin() to make sure it for this plugin.
        Object obj = mdList.get( 0 ).value();
        if ( obj instanceof Location ) {
            Location spawnLoc = (Location) obj;
            if ( spawnLoc.distanceSquared( player.getLocation() ) <= r2 
                    || spawnLoc.distanceSquared( entity.getLocation() ) <= r2 )
                return true;
        }

        return false;
    }

The above snippets are hand-typed into this message and haven't been checked for syntax. At least they should give you a good idea of an alternative approach.

Best,
Frelling

commented

Good catch. Definitely will implement this optimization shortly.

commented

Confirmed! CampByDistance now functions as expected. Great job on the quick update.