Permalink
Browse files

Fix and optimize entities_in_radius(), and avoid unnecessary chunk lo…

…ading.

The chunk radius was off by one and would miss entities on the outside chunks. It would also ignore any entities in the same block location as the center location. Chunks were also being loaded by this and some other functions, causing unexpected performance problems.
  • Loading branch information...
PseudoKnight committed Dec 22, 2018
1 parent 16d1c45 commit 3aaeaed992e56cff1703fa2a64f7774204d653fb
@@ -72,6 +72,8 @@

MCChunk[] getLoadedChunks();

boolean isChunkLoaded(int x, int z);

boolean regenerateChunk(int x, int y);

MCEntity spawn(MCLocation l, Class mobType);
@@ -926,6 +926,11 @@ public MCChunk getChunkAt(MCLocation l) {
return mcChunks;
}

@Override
public boolean isChunkLoaded(int x, int z) {
return w.isChunkLoaded(x, z);
}

@Override
public void setThundering(boolean b) {
w.setThundering(b);
@@ -121,10 +121,8 @@

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -251,7 +249,7 @@ public String getName() {
public String docs() {
return "array {[world, [x, z]] | [locationArray]} Returns an array of IDs for all entities in the given"
+ " scope. With no args, this will return all entities loaded on the entire server. If the first"
+ " argument is given and is a location, only entities in the chunk containin that location will"
+ " argument is given and is a location, only entities in the chunk containing that location will"
+ " be returned, or if it is a world only entities in that world will be returned. If all three"
+ " arguments are given, only entities in the chunk with those coords will be returned. This can"
+ " take chunk coords (ints) or location coords (doubles).";
@@ -891,6 +889,10 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntime
loc = ObjectGenerator.GetGenerator().location(args[0], p != null ? p.getWorld() : null, t);
dist = Static.getInt32(args[1], t);

if(dist < 0) {
throw new CRERangeException("Distance cannot be negative.", t);
}

if(args.length == 3) {
if(args[2] instanceof CArray) {
CArray ta = (CArray) args[2];
@@ -903,37 +905,33 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntime
types = prepareTypes(t, types);
}

// The idea and code comes from skore87 (http://forums.bukkit.org/members/skore87.105075/)
// http://forums.bukkit.org/threads/getnearbyentities-of-a-location.101499/#post-1341141
int chunkRadius = dist < 16 ? 1 : (dist - (dist % 16)) / 16;
MCWorld world = loc.getWorld();
int chunkRadius = (dist + 16) / 16;
int distanceSquared = dist * dist;
int centerX = loc.getBlockX() >> 4;
int centerZ = loc.getBlockZ() >> 4;

Set<UUID> eSet = new HashSet<>();
for(int chX = 0 - chunkRadius; chX <= chunkRadius; chX++) {
for(int chZ = 0 - chunkRadius; chZ <= chunkRadius; chZ++) {
MCLocation nl = StaticLayer.GetLocation(loc.getWorld(), loc.getX() + (chX * 16), loc.getY(), loc.getZ() + (chZ * 16));
for(MCEntity e : nl.getChunk().getEntities()) {
if(!e.getWorld().equals(loc.getWorld())) {
// We can't measure entity distances that are in different worlds!
continue;
}
if(e.getLocation().distance(loc) <= dist && e.getLocation().getBlock() != loc.getBlock()) {
CArray entities = new CArray(t);
for(int offsetX = 0 - chunkRadius; offsetX <= chunkRadius; offsetX++) {
for(int offsetZ = 0 - chunkRadius; offsetZ <= chunkRadius; offsetZ++) {
if(!world.isChunkLoaded(centerX + offsetX, centerZ + offsetZ)) {
continue;
}
for(MCEntity e : world.getChunkAt(centerX + offsetX, centerZ + offsetZ).getEntities()) {
if(e.getLocation().distanceSquared(loc) <= distanceSquared) {
if(types.isEmpty() || types.contains(e.getType().name())) {
eSet.add(e.getUniqueId());
entities.push(new CString(e.getUniqueId().toString(), t), t);
}
}
}
}
}
CArray entities = new CArray(t);
for(UUID e : eSet) {
entities.push(new CString(e.toString(), t), t);
}
return entities;
}

private List<String> prepareTypes(Target t, List<String> types) {
List<String> newTypes = new ArrayList<String>();
MCEntityType entityType = null;
List<String> newTypes = new ArrayList<>();
MCEntityType entityType;
for(String type : types) {
try {
entityType = MCEntityType.valueOf(type.toUpperCase());
@@ -947,15 +945,16 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntime

@Override
public String docs() {
return "array {location array, distance, [type] | location array, distance, [arrayTypes]} Returns an array of"
+ " all entities within the given radius. Set type argument to filter entities to a specific type. You"
+ " can pass an array of types. Valid types (case doesn't matter): "
return "array {locationArray, distance, [type] | locationArray, distance, [arrayTypes]} Returns an array of"
+ " all entities within the given distance from the location. Set type argument to filter entities"
+ " to a specific entity type. You can pass an array of types. Valid types (case doesn't matter): "
+ StringUtils.Join(MCEntityType.types(), ", ", ", or ", " or ");
}

@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class, CREBadEntityException.class, CREFormatException.class};
return new Class[]{CRECastException.class, CREBadEntityException.class, CREFormatException.class,
CRERangeException.class};
}

@Override
@@ -574,8 +574,8 @@ public Mixed exec(Target t, Environment environment, Mixed... args) throws Confi
MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t);

world = l.getWorld();
x = l.getChunk().getX();
z = l.getChunk().getZ();
x = l.getBlockX() >> 4;
z = l.getBlockZ() >> 4;
} else if(args.length == 2) {
//Either location array and world provided, or x and z. Test for array at pos 1
if(args[0] instanceof CArray) {
@@ -585,8 +585,8 @@ public Mixed exec(Target t, Environment environment, Mixed... args) throws Confi
}
MCLocation l = ObjectGenerator.GetGenerator().location(args[0], null, t);

x = l.getChunk().getX();
z = l.getChunk().getZ();
x = l.getBlockX() >> 4;
z = l.getBlockZ() >> 4;
} else {
if(m == null) {
throw new CREInvalidWorldException("No world specified", t);
@@ -661,8 +661,8 @@ public Mixed exec(Target t, Environment environment, Mixed... args) throws Confi
//Location array provided
MCLocation l = ObjectGenerator.GetGenerator().location(args[0], m != null ? m.getWorld() : null, t);
world = l.getWorld();
x = l.getChunk().getX();
z = l.getChunk().getZ();
x = l.getBlockX() >> 4;
z = l.getBlockZ() >> 4;
} else if(args.length == 2) {
//Either location array and world provided, or x and z. Test for array at pos 1
if(args[0] instanceof CArray) {
@@ -671,8 +671,8 @@ public Mixed exec(Target t, Environment environment, Mixed... args) throws Confi
throw new CREInvalidWorldException("The given world (" + args[1].val() + ") does not exist.", t);
}
MCLocation l = ObjectGenerator.GetGenerator().location(args[0], null, t);
x = l.getChunk().getX();
z = l.getChunk().getZ();
x = l.getBlockX() >> 4;
z = l.getBlockZ() >> 4;
} else {
if(m == null) {
throw new CREInvalidWorldException("No world specified", t);
@@ -1045,13 +1045,13 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommand
}
}

CArray chunk = new CArray(t,
new CInt(l.getChunk().getX(), t),
new CInt(l.getChunk().getZ(), t),
new CString(l.getChunk().getWorld().getName(), t));
chunk.set("x", new CInt(l.getChunk().getX(), t), t);
chunk.set("z", new CInt(l.getChunk().getZ(), t), t);
chunk.set("world", l.getChunk().getWorld().getName(), t);
CArray chunk = CArray.GetAssociativeArray(t);
chunk.set(0, new CInt(l.getBlockX() >> 4, t), t);
chunk.set(1, new CInt(l.getBlockZ() >> 4, t), t);
chunk.set(2, new CString(l.getWorld().getName(), t), t);
chunk.set("x", new CInt(l.getBlockX() >> 4, t), t);
chunk.set("z", new CInt(l.getBlockZ() >> 4, t), t);
chunk.set("world", l.getWorld().getName(), t);
return chunk;
}

0 comments on commit 3aaeaed

Please sign in to comment.