diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 171c1cae2..1ef9b94e4 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -862,6 +862,16 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P return getPosition().getDistance(entity.getPosition()); } + /** + * Gets the distance squared between two entities. + * + * @param entity the entity to get the distance from + * @return the distance squared between this and {@code entity} + */ + public double getDistanceSquared(@NotNull Entity entity) { + return getPosition().getDistanceSquared(entity.getPosition()); + } + /** * Gets the entity vehicle or null. * diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 0759d732d..9f6d49e8c 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -22,6 +22,7 @@ import net.minestom.server.sound.Sound; import net.minestom.server.sound.SoundCategory; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; +import net.minestom.server.utils.Vector; import net.minestom.server.utils.block.BlockIterator; import net.minestom.server.utils.time.CooldownUtils; import net.minestom.server.utils.time.TimeUnit; @@ -590,6 +591,30 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { return blocks; } + /** + * Checks whether the current entity has line of sight to the given one. + * If so, it doesn't mean that the given entity is IN line of sight of the current, + * but the current one can rotate so that it will be true. + * + * @param entity the entity to be checked. + * @return if the current entity has line of sight to the given one. + */ + public boolean hasLineOfSight(Entity entity) { + Vector start = getPosition().toVector().add(0D, getEyeHeight(), 0D); + Vector end = entity.getPosition().toVector().add(0D, getEyeHeight(), 0D); + Vector direction = end.subtract(start); + int maxDistance = (int) Math.ceil(direction.length()); + + Iterator it = new BlockIterator(start, direction.normalize(), 0D, maxDistance); + while (it.hasNext()) { + Block block = Block.fromStateId(getInstance().getBlockStateId(it.next())); + if (!block.isAir() && !block.isLiquid()) { + return false; + } + } + return true; + } + /** * Gets the target (not-air) {@link BlockPosition} of the entity. * diff --git a/src/main/java/net/minestom/server/entity/ai/goal/RangedAttackGoal.java b/src/main/java/net/minestom/server/entity/ai/goal/RangedAttackGoal.java new file mode 100644 index 000000000..8f45a1797 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/ai/goal/RangedAttackGoal.java @@ -0,0 +1,100 @@ +package net.minestom.server.entity.ai.goal; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.ai.GoalSelector; +import net.minestom.server.entity.pathfinding.Navigator; +import net.minestom.server.utils.Position; +import net.minestom.server.utils.time.CooldownUtils; +import net.minestom.server.utils.time.TimeUnit; +import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.NotNull; + +/** + * Created by k.shandurenko on 22.02.2021 + */ +public class RangedAttackGoal extends GoalSelector { + + private long lastShot; + private final int delay; + private final TimeUnit timeUnit; + private final int attackRangeSquared; + private final int desirableRangeSquared; + private final boolean comeClose; + + private boolean stop; + + /** + * @param entityCreature the entity to add the goal to. + * @param delay the delay between each shots. + * @param attackRange the allowed range the entity can shoot others. + * @param desirableRange the desirable range: the entity will try to stay no further than this distance. + * @param comeClose whether entity should go as close as possible to the target whether target is not in line of sight. + * @param timeUnit the unit of the delay. + */ + public RangedAttackGoal(@NotNull EntityCreature entityCreature, int delay, int attackRange, int desirableRange, boolean comeClose, @NotNull TimeUnit timeUnit) { + super(entityCreature); + this.delay = delay; + this.timeUnit = timeUnit; + this.attackRangeSquared = attackRange * attackRange; + this.desirableRangeSquared = desirableRange * desirableRange; + this.comeClose = comeClose; + Check.argCondition(desirableRange <= attackRange, "Desirable range can not exceed attack range!"); + } + + @Override + public boolean shouldStart() { + return findAndUpdateTarget() != null; + } + + @Override + public void start() { + Entity target = findAndUpdateTarget(); + Check.notNull(target, "The target is not expected to be null!"); + this.entityCreature.getNavigator().setPathTo(target.getPosition()); + } + + @Override + public void tick(long time) { + Entity target = findAndUpdateTarget(); + if (target == null) { + this.stop = true; + return; + } + double distanceSquared = this.entityCreature.getDistanceSquared(target); + boolean comeClose = false; + if (distanceSquared <= this.attackRangeSquared) { + if (!CooldownUtils.hasCooldown(time, this.lastShot, this.timeUnit, this.delay)) { + if (this.entityCreature.hasLineOfSight(target)) { + + this.lastShot = time; + } else { + comeClose = this.comeClose; + } + } + } + Navigator navigator = this.entityCreature.getNavigator(); + Position pathPosition = navigator.getPathPosition(); + if (!comeClose && distanceSquared <= this.desirableRangeSquared) { + if (pathPosition != null) { + navigator.setPathTo(null); + } + return; + } + Position targetPosition = target.getPosition(); + if (pathPosition == null || !pathPosition.isSimilar(targetPosition)) { + navigator.setPathTo(targetPosition); + } + } + + @Override + public boolean shouldEnd() { + return this.stop; + } + + @Override + public void end() { + // Stop following the target + this.entityCreature.getNavigator().setPathTo(null); + } +} diff --git a/src/main/java/net/minestom/server/utils/block/BlockIterator.java b/src/main/java/net/minestom/server/utils/block/BlockIterator.java index 53e622532..ec24391fa 100644 --- a/src/main/java/net/minestom/server/utils/block/BlockIterator.java +++ b/src/main/java/net/minestom/server/utils/block/BlockIterator.java @@ -345,23 +345,19 @@ public class BlockIterator implements Iterator { thirdError -= gridSize; secondError -= gridSize; currentBlock = 2; - return; } else if (secondError > 0) { blockQueue[1] = blockQueue[0].getRelative(mainFace); blockQueue[0] = blockQueue[1].getRelative(secondFace); secondError -= gridSize; currentBlock = 1; - return; } else if (thirdError > 0) { blockQueue[1] = blockQueue[0].getRelative(mainFace); blockQueue[0] = blockQueue[1].getRelative(thirdFace); thirdError -= gridSize; currentBlock = 1; - return; } else { blockQueue[0] = blockQueue[0].getRelative(mainFace); currentBlock = 0; - return; } }