From 7b5c6dfc44e524e6893ffa999c0735fd14e79fe2 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Sun, 9 Aug 2020 08:16:54 +0200 Subject: [PATCH] Added LastEntityDamagerTarget & LivingEntity#getLastDamageType --- .../net/minestom/server/entity/Entity.java | 11 +++++ .../minestom/server/entity/LivingEntity.java | 16 +++++++- .../entity/ai/goal/MeleeAttackGoal.java | 25 +++++++++-- .../entity/ai/target/ClosestEntityTarget.java | 14 +++++-- .../ai/target/LastEntityDamagerTarget.java | 41 +++++++++++++++++++ 5 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 80e0e239f..4d0b4ca58 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -77,6 +77,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { protected UUID uuid; private boolean isActive; // False if entity has only been instanced without being added somewhere + private boolean removed; private boolean shouldRemove; private long scheduledRemoveTime; private EntityType entityType; @@ -1116,12 +1117,22 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { * WARNING: this do not trigger the {@link EntityDeathEvent} event */ public void remove() { + this.removed = true; this.shouldRemove = true; entityById.remove(id); if (instance != null) instance.removeEntity(this); } + /** + * Get if this entity has been removed + * + * @return true if this entity is removed + */ + public boolean isRemoved() { + return removed; + } + /** * Trigger {@link #remove()} after the specified time * diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index eb51b3390..24555011c 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -27,6 +27,7 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { protected boolean isDead; private float health; + private DamageType lastDamageType; // Bounding box used for items' pickup (see LivingEntity#setBoundingBox) protected BoundingBox expandedBoundingBox; @@ -244,7 +245,7 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { // Additional hearts support if (this instanceof Player) { - Player player = (Player) this; + final Player player = (Player) this; final float additionalHearts = player.getAdditionalHearts(); if (additionalHearts > 0) { if (damage > additionalHearts) { @@ -257,6 +258,7 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { } } + // Set the final entity health setHealth(getHealth() - damage); // play damage sound @@ -273,6 +275,9 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { SoundEffectPacket damageSoundPacket = SoundEffectPacket.create(soundCategory, sound, getPosition().getX(), getPosition().getY(), getPosition().getZ(), 1.0f, 1.0f); sendPacketToViewersAndSelf(damageSoundPacket); } + + // Set the last damage type since the event is not cancelled + this.lastDamageType = entityDamageEvent.getDamageType(); }); return !entityDamageEvent.isCancelled(); @@ -312,6 +317,15 @@ public abstract class LivingEntity extends Entity implements EquipmentHandler { sendMetadataIndex(8); // Health metadata index } + /** + * Get the last damage type of this entity + * + * @return the last damage type, null if not any + */ + public DamageType getLastDamageType() { + return lastDamageType; + } + /** * Get the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#MAX_HEALTH} * diff --git a/src/main/java/net/minestom/server/entity/ai/goal/MeleeAttackGoal.java b/src/main/java/net/minestom/server/entity/ai/goal/MeleeAttackGoal.java index 9d5f14cae..547a921fc 100644 --- a/src/main/java/net/minestom/server/entity/ai/goal/MeleeAttackGoal.java +++ b/src/main/java/net/minestom/server/entity/ai/goal/MeleeAttackGoal.java @@ -3,10 +3,15 @@ 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.ai.TargetSelector; import net.minestom.server.utils.Position; import net.minestom.server.utils.time.CooldownUtils; import net.minestom.server.utils.time.TimeUnit; +/** + * Attack the entity's target ({@link EntityCreature#getTarget()}) OR the closest entity + * which can be targeted with the entity {@link TargetSelector} + */ public class MeleeAttackGoal extends GoalSelector { private long lastHit; @@ -26,20 +31,21 @@ public class MeleeAttackGoal extends GoalSelector { @Override public boolean shouldStart() { - return entityCreature.getTarget() != null; + return getTarget() != null; } @Override public void start() { - final Position targetPosition = entityCreature.getTarget().getPosition(); + final Position targetPosition = getTarget().getPosition(); entityCreature.setPathTo(targetPosition); } @Override public void tick(long time) { - final Entity target = entityCreature.getTarget(); + final Entity target = getTarget(); if (target != null) { + // Attack the target entity if (entityCreature.getBoundingBox().intersect(target)) { if (!CooldownUtils.hasCooldown(time, lastHit, timeUnit, delay)) { entityCreature.attack(target, true); @@ -48,6 +54,7 @@ public class MeleeAttackGoal extends GoalSelector { return; } + // Move toward the target entity final Position pathPosition = entityCreature.getPathPosition(); final Position targetPosition = target.getPosition(); if (pathPosition == null || !pathPosition.isSimilar(targetPosition)) { @@ -63,6 +70,18 @@ public class MeleeAttackGoal extends GoalSelector { @Override public void end() { + // Stop following the target + entityCreature.setPathTo(null); + } + /** + * Use {@link EntityCreature#getTarget()} or + * the entity target selectors to get the correct target + * + * @return the target of the entity + */ + private Entity getTarget() { + final Entity target = entityCreature.getTarget(); + return target == null ? findTarget() : target; } } diff --git a/src/main/java/net/minestom/server/entity/ai/target/ClosestEntityTarget.java b/src/main/java/net/minestom/server/entity/ai/target/ClosestEntityTarget.java index 902fc6d92..679800c1f 100644 --- a/src/main/java/net/minestom/server/entity/ai/target/ClosestEntityTarget.java +++ b/src/main/java/net/minestom/server/entity/ai/target/ClosestEntityTarget.java @@ -12,6 +12,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +/** + * Target the closest entity + */ public class ClosestEntityTarget extends TargetSelector { private float range; @@ -37,11 +40,13 @@ public class ClosestEntityTarget extends TargetSelector { for (Chunk chunk : chunks) { final Set entities = instance.getChunkEntities(chunk); - if (!(entities instanceof LivingEntity)) - continue; - for (Entity ent : entities) { + // Only target living entities + if (!(ent instanceof LivingEntity)) { + continue; + } + // Don't target itself if (ent.equals(entityCreature)) { continue; @@ -56,12 +61,13 @@ public class ClosestEntityTarget extends TargetSelector { break; } } + if (!correct) { continue; } // Check distance - final float d = getEntityCreature().getDistance(ent); + final float d = entityCreature.getDistance(ent); if ((entity == null || d < distance) && d < range) { entity = ent; distance = d; diff --git a/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java b/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java new file mode 100644 index 000000000..a15783713 --- /dev/null +++ b/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java @@ -0,0 +1,41 @@ +package net.minestom.server.entity.ai.target; + +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.ai.TargetSelector; +import net.minestom.server.entity.damage.DamageType; +import net.minestom.server.entity.damage.EntityDamage; + +/** + * Target the last damager of this entity + */ +public class LastEntityDamagerTarget extends TargetSelector { + + private float range; + + public LastEntityDamagerTarget(EntityCreature entityCreature, float range) { + super(entityCreature); + this.range = range; + } + + @Override + public Entity findTarget() { + final DamageType damageType = entityCreature.getLastDamageType(); + + if (!(damageType instanceof EntityDamage)) { + // No damager recorded, return null + return null; + } + + final EntityDamage entityDamage = (EntityDamage) damageType; + final Entity entity = entityDamage.getSource(); + + if (entity.isRemoved()) { + // Entity not valid + return null; + } + + // Check range + return entityCreature.getDistance(entity) < range ? entity : null; + } +}