Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cuboid syntax to ExprEntities #6900

Merged
merged 19 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 87 additions & 47 deletions src/main/java/ch/njol/skript/expressions/ExprEntities.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.util.BoundingBox;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;

import java.lang.reflect.Array;
import java.util.ArrayList;
Expand All @@ -50,53 +52,65 @@
import java.util.List;

@Name("Entities")
@Description("All entities in all worlds, in a specific world, in a chunk or in a radius around a certain location, " +
@Description("All entities in all worlds, in a specific world, in a chunk, in a radius around a certain location or within two locations. " +
"e.g. <code>all players</code>, <code>all creepers in the player's world</code>, or <code>players in radius 100 of the player</code>.")
@Examples({"kill all creepers in the player's world",
"send \"Psst!\" to all players within 100 meters of the player",
"give a diamond to all ops",
"heal all tamed wolves in radius 2000 around {town center}",
"delete all monsters in chunk at player"})
@Since("1.2.1, 2.5 (chunks)")
"delete all monsters in chunk at player",
"size of all players within {_corner::1} and {_corner::2}}"})
@Since("1.2.1, 2.5 (chunks), INSERT VERSION (within)")
public class ExprEntities extends SimpleExpression<Entity> {

static {
Skript.registerExpression(ExprEntities.class, Entity.class, ExpressionType.PATTERN_MATCHES_EVERYTHING,
"[(all [[of] the]|the)] %*entitydatas% [(in|of) ([world[s]] %-worlds%|1¦%-chunks%)]",
"[(all [[of] the]|the)] entities of type[s] %entitydatas% [(in|of) ([world[s]] %-worlds%|1¦%-chunks%)]",
"[(all [[of] the]|the)] %*entitydatas% (within|[with]in radius) %number% [(block[s]|met(er|re)[s])] (of|around) %location%",
"[(all [[of] the]|the)] entities of type[s] %entitydatas% in radius %number% (of|around) %location%");
"[(all [[of] the]|the)] entities of type[s] %entitydatas% in radius %number% (of|around) %location%",
"[(all [[of] the]|the)] %*entitydatas% within %location% and %location%",
"[(all [[of] the]|the)] entities of type[s] %entitydatas% within %location% and %location%");
}

@SuppressWarnings("null")
Expression<? extends EntityData<?>> types;

@Nullable
@UnknownNullability
private Expression<World> worlds;
@Nullable
@UnknownNullability
private Expression<Chunk> chunks;
@Nullable
@UnknownNullability
private Expression<Number> radius;
@Nullable
@UnknownNullability
private Expression<Location> center;
@UnknownNullability
private Expression<Location> from;
@UnknownNullability
private Expression<Location> to;

private Class<? extends Entity> returnType = Entity.class;
private boolean isUsingRadius;
private boolean isUsingCuboid;

@Override
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
types = (Expression<? extends EntityData<?>>) exprs[0];
if (matchedPattern % 2 == 0) {
for (EntityData<?> d : ((Literal<EntityData<?>>) types).getAll()) {
if (d.isPlural().isFalse() || d.isPlural().isUnknown() && !StringUtils.startsWithIgnoreCase(parseResult.expr, "all"))
for (EntityData<?> entityType : ((Literal<EntityData<?>>) types).getAll()) {
if (entityType.isPlural().isFalse() || entityType.isPlural().isUnknown() && !StringUtils.startsWithIgnoreCase(parseResult.expr, "all"))
return false;
}
}
isUsingRadius = matchedPattern >= 2;
isUsingRadius = matchedPattern == 2 || matchedPattern == 3;
isUsingCuboid = matchedPattern >= 4;
if (isUsingRadius) {
radius = (Expression<Number>) exprs[exprs.length - 2];
center = (Expression<Location>) exprs[exprs.length - 1];
radius = (Expression<Number>) exprs[1];
center = (Expression<Location>) exprs[2];
} else if (isUsingCuboid) {
from = (Expression<Location>) exprs[1];
to = (Expression<Location>) exprs[2];
} else {
if (parseResult.mark == 1) {
chunks = (Expression<Chunk>) exprs[2];
Expand All @@ -115,11 +129,11 @@ public boolean isLoopOf(String s) {
if (!(types instanceof Literal<?>))
return false;
try (LogHandler ignored = new BlockingLogHandler().start()) {
EntityData<?> d = EntityData.parseWithoutIndefiniteArticle(s);
if (d != null) {
for (EntityData<?> t : ((Literal<EntityData<?>>) types).getAll()) {
assert t != null;
if (!d.isSupertypeOf(t))
EntityData<?> entityData = EntityData.parseWithoutIndefiniteArticle(s);
if (entityData != null) {
for (EntityData<?> entityType : ((Literal<EntityData<?>>) types).getAll()) {
assert entityType != null;
if (!entityData.isSupertypeOf(entityType))
return false;
}
return true;
Expand All @@ -129,62 +143,82 @@ public boolean isLoopOf(String s) {
}

@Override
@Nullable
@SuppressWarnings("null")
protected Entity[] get(Event e) {
if (isUsingRadius) {
Iterator<? extends Entity> iter = iterator(e);
protected Entity @Nullable [] get(Event event) {
if (isUsingRadius || isUsingCuboid) {
Iterator<? extends Entity> iter = iterator(event);
if (iter == null || !iter.hasNext())
return null;

List<Entity> l = new ArrayList<>();
List<Entity> list = new ArrayList<>();
while (iter.hasNext())
l.add(iter.next());
return l.toArray((Entity[]) Array.newInstance(returnType, l.size()));
list.add(iter.next());
return list.toArray((Entity[]) Array.newInstance(returnType, list.size()));
} else {
if (chunks != null) {
return EntityData.getAll(types.getArray(e), returnType, chunks.getArray(e));
return EntityData.getAll(types.getArray(event), returnType, chunks.getArray(event));
} else {
return EntityData.getAll(types.getAll(e), returnType, worlds != null ? worlds.getArray(e) : null);
return EntityData.getAll(types.getAll(event), returnType, worlds != null ? worlds.getArray(event) : null);
}
}
}

@Override
@Nullable
@SuppressWarnings("null")
public Iterator<? extends Entity> iterator(Event e) {
public Iterator<? extends Entity> iterator(Event event) {
if (isUsingRadius) {
assert center != null;
Location l = center.getSingle(e);
if (l == null)
Location location = center.getSingle(event);
if (location == null)
return null;
assert radius != null;
Number n = radius.getSingle(e);
if (n == null)
Number number = radius.getSingle(event);
if (number == null)
return null;
double d = n.doubleValue();
double rad = number.doubleValue();

if (l.getWorld() == null) // safety
if (location.getWorld() == null) // safety
return null;

Collection<Entity> es = l.getWorld().getNearbyEntities(l, d, d, d);
double radiusSquared = d * d * Skript.EPSILON_MULT;
EntityData<?>[] ts = types.getAll(e);
return new CheckedIterator<>(es.iterator(), e1 -> {
if (e1 == null || e1.getLocation().distanceSquared(l) > radiusSquared)
Collection<Entity> nearbyEntities = location.getWorld().getNearbyEntities(location, rad, rad, rad);
double radiusSquared = rad * rad * Skript.EPSILON_MULT;
EntityData<?>[] entityTypes = types.getAll(event);
return new CheckedIterator<>(nearbyEntities.iterator(), entity -> {
if (entity == null || entity.getLocation().distanceSquared(location) > radiusSquared)
return false;
for (EntityData<?> t : ts) {
if (t.isInstance(e1))
for (EntityData<?> entityType : entityTypes) {
if (entityType.isInstance(entity))
return true;
}
return false;
});
} else if (isUsingCuboid) {
Location corner1 = from.getSingle(event);
if (corner1 == null)
return null;
Location corner2 = to.getSingle(event);
if (corner2 == null)
return null;
EntityData<?>[] entityTypes = types.getAll(event);
World world = corner1.getWorld();
if (world == null)
world = corner2.getWorld();
if (world == null)
return null;
Collection<Entity> entities = corner1.getWorld().getNearbyEntities(BoundingBox.of(corner1, corner2));
return new CheckedIterator<>(entities.iterator(), entity -> {
if (entity == null)
return false;
for (EntityData<?> entityType : entityTypes) {
if (entityType.isInstance(entity))
return true;
}
return false;
});
} else {
if (chunks == null || returnType == Player.class)
return super.iterator(e);
return super.iterator(event);

return Arrays.stream(EntityData.getAll(types.getArray(e), returnType, chunks.getArray(e))).iterator();
return Arrays.stream(EntityData.getAll(types.getArray(event), returnType, chunks.getArray(event))).iterator();
}
}

Expand All @@ -201,8 +235,14 @@ public Class<? extends Entity> getReturnType() {
@Override
@SuppressWarnings("null")
public String toString(@Nullable Event e, boolean debug) {
return "all entities of type " + types.toString(e, debug) + (worlds != null ? " in " + worlds.toString(e, debug) :
radius != null && center != null ? " in radius " + radius.toString(e, debug) + " around " + center.toString(e, debug) : "");
String message = "all entities of type " + types.toString(e, debug);
if (worlds != null)
message += " in " + worlds.toString(e, debug);
else if (radius != null && center != null)
message += " in radius " + radius.toString(e, debug) + " around " + center.toString(e, debug);
else if (from != null && to != null)
message += " within " + from.toString(e, debug) + " and " + to.toString(e, debug);
return message;
}

}
45 changes: 41 additions & 4 deletions src/test/skript/tests/syntaxes/expressions/ExprEntities.sk
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,49 @@ test "entities in chunk":
add event-entity to {_sheep::*}
add 1 to {_count}
assert {_count} and size of entities within {_sheep::*} is 10 with "Did not count 10 on the entities within {_sheep::*}: %{_sheep::*}%"
assert size of all entities in chunk at spawn of world "world" >= 10 with "Size of all entities in spawn chunk is not > 10: %size of all entities in chunk at spawn of world "world"%"

loop all sheep in chunk at spawn of world "world":
add loop-entity to {_e::*}
assert size of {_e::*} >= 10 with "Size of all sheep in spawn chunk is not >= 10 (iterating): %size of {_e::*}%"
set {_radius center} to location(-24, -60, -24, world "world")
spawn 2 sheep at location(-26, -60, -24, world "world")
spawn 2 sheep at location(-25, -58, -25, world "world")
spawn 2 sheep at location(-24, -60, -23, world "world")
spawn 2 sheep at location(-23, -60, -22, world "world")
spawn 2 sheep at location(-22, -60, -25, world "world")

set {_corner::1} to location(20, -62, 20, world "world")
set {_corner::2} to location(28, -58, 28, world "world")
spawn 2 sheep at location(21, -60, 21, world "world")
spawn 2 sheep at location(22, -59, 25, world "world")
spawn 2 sheep at location(24, -60, 23, world "world")
spawn 2 sheep at location(26, -60, 28, world "world")
spawn 2 sheep at location(28, -60, 20, world "world")

assert size of all entities in chunk at spawn of world "world" >= 10 with "Size of all entities in spawn chunk is not >= 10"
assert size of all entities in radius 3 of {_radius center} >= 10 with "Size of all entities in radius 3 of spawn is not >= 10"
assert size of all entities within {_corner::1} and {_corner::2} >= 10 with "Size of all entities within cuboid is not >= 10"


loop all entities in chunk at spawn of world "world":
add loop-entity to {_chunk::*}
assert size of {_chunk::*} >= 10 with "Size of all entities in spawn chunk is not >= 10 (iterating)"

loop all sheep in radius 3 of {_radius center}:
add loop-entity to {_radius::*}
assert size of {_radius::*} >= 10 with "Size of all sheep in radius 3 of spawn is not >= 10 (iterating)"

loop all sheep within {_corner::1} and {_corner::2}:
add loop-entity to {_cuboid::*}
assert size of {_cuboid::*} >= 10 with "Size of all sheep within cuboid is not >= 10 (iterating)"


delete all entities in chunk at spawn of world "world"
assert size of all entities in chunk at spawn of world "world" = 0 with "Size of all entities in spawn chunk != 0"

delete all entities in radius 3 of {_radius center}
assert size of all entities in radius 3 of {_radius center} = 0 with "Size of all entities in radius 3 of spawn != 0"

delete all entities within {_corner::1} and {_corner::2}
assert size of all entities within {_corner::1} and {_corner::2} = 0 with "Size of all entities in cuboid != 0"

clear entities within {_e::*}
assert entities within {_e::*} where [entity input is valid] is not set with "Not all the sheep were cleared"
clear all entities