Minestom/src/main/java/net/minestom/server/entity/EntityCreature.java

399 lines
12 KiB
Java
Raw Normal View History

2020-04-24 03:25:58 +02:00
package net.minestom.server.entity;
2019-08-10 08:44:35 +02:00
2020-07-24 02:31:10 +02:00
import com.extollit.gaming.ai.path.HydrazinePathFinder;
2020-09-01 21:16:07 +02:00
import com.extollit.gaming.ai.path.model.IPath;
2020-11-17 14:47:49 +01:00
import net.minestom.server.attribute.Attributes;
import net.minestom.server.entity.ai.EntityAI;
2020-08-06 11:56:43 +02:00
import net.minestom.server.entity.ai.GoalSelector;
import net.minestom.server.entity.ai.TargetSelector;
import net.minestom.server.entity.pathfinding.NavigableEntity;
2020-07-24 02:49:55 +02:00
import net.minestom.server.entity.pathfinding.PFPathingEntity;
2020-05-28 19:15:55 +02:00
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.item.ArmorEquipEvent;
2020-07-24 02:31:10 +02:00
import net.minestom.server.instance.Instance;
import net.minestom.server.item.ItemStack;
2020-08-07 06:36:03 +02:00
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
import net.minestom.server.network.packet.server.play.EntityMovementPacket;
2020-08-07 06:36:03 +02:00
import net.minestom.server.network.packet.server.play.SpawnLivingEntityPacket;
2020-04-24 03:25:58 +02:00
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.Position;
2020-05-15 18:03:28 +02:00
import net.minestom.server.utils.time.TimeUnit;
2020-10-24 10:46:23 +02:00
import org.jetbrains.annotations.NotNull;
2020-10-24 11:19:54 +02:00
import org.jetbrains.annotations.Nullable;
2019-08-10 08:44:35 +02:00
2020-08-06 11:56:43 +02:00
import java.util.ArrayList;
2020-12-05 01:36:06 +01:00
import java.util.HashSet;
2020-08-06 11:56:43 +02:00
import java.util.List;
2020-12-05 01:36:06 +01:00
import java.util.Set;
2020-08-06 11:56:43 +02:00
public abstract class EntityCreature extends LivingEntity implements NavigableEntity, EntityAI {
private int removalAnimationDelay = 1000;
// TODO all pathfinding requests should be process in another thread
private final Object pathLock = new Object();
2020-03-29 20:58:30 +02:00
2020-09-24 01:50:25 +02:00
private final PFPathingEntity pathingEntity = new PFPathingEntity(this);
2020-07-24 02:31:10 +02:00
private HydrazinePathFinder pathFinder;
2020-09-01 21:16:07 +02:00
private IPath path;
2020-08-06 13:46:30 +02:00
private Position pathPosition;
2020-03-29 20:58:30 +02:00
2020-10-22 12:55:53 +02:00
protected final List<GoalSelector> goalSelectors = new ArrayList<>();
protected final List<TargetSelector> targetSelectors = new ArrayList<>();
2020-08-06 11:56:43 +02:00
private GoalSelector currentGoalSelector;
private Entity target;
2020-12-05 01:36:06 +01:00
/**
* Lock used to support #switchEntityType
*/
2020-12-05 16:09:08 +01:00
private final Object entityTypeLock = new Object();
2020-12-05 01:36:06 +01:00
// Equipments
private ItemStack mainHandItem;
private ItemStack offHandItem;
private ItemStack helmet;
private ItemStack chestplate;
private ItemStack leggings;
private ItemStack boots;
public EntityCreature(@NotNull EntityType entityType, @NotNull Position spawnPosition) {
super(entityType, spawnPosition);
2020-05-23 04:20:01 +02:00
this.mainHandItem = ItemStack.getAirItem();
this.offHandItem = ItemStack.getAirItem();
this.helmet = ItemStack.getAirItem();
this.chestplate = ItemStack.getAirItem();
this.leggings = ItemStack.getAirItem();
this.boots = ItemStack.getAirItem();
2020-05-25 19:54:36 +02:00
heal();
2019-08-10 08:44:35 +02:00
}
public EntityCreature(@NotNull EntityType entityType, @NotNull Position spawnPosition, @Nullable Instance instance) {
this(entityType, spawnPosition);
if (instance != null) {
setInstance(instance);
}
}
2019-08-24 20:34:01 +02:00
@Override
public void update(long time) {
// AI
aiTick(time);
2020-08-06 11:56:43 +02:00
2020-04-09 14:25:42 +02:00
// Path finding
pathFindingTick(getAttributeValue(Attributes.MOVEMENT_SPEED));
// Fire, item pickup, ...
super.update(time);
2020-07-24 02:31:10 +02:00
}
@Override
2020-10-29 22:52:07 +01:00
public void setInstance(@NotNull Instance instance) {
2020-07-24 02:31:10 +02:00
super.setInstance(instance);
this.pathFinder = new HydrazinePathFinder(pathingEntity, instance.getInstanceSpace());
2019-08-24 20:34:01 +02:00
}
@Override
public void spawn() {
}
2019-08-24 20:34:01 +02:00
@Override
2019-08-21 16:50:52 +02:00
public void kill() {
2020-04-05 10:15:21 +02:00
super.kill();
if (removalAnimationDelay > 0) {
// Needed for proper death animation (wait for it to finish before destroying the entity)
scheduleRemove(removalAnimationDelay, TimeUnit.MILLISECOND);
} else {
// Instant removal without animation playback
remove();
}
2019-08-11 13:57:23 +02:00
}
2019-08-19 17:04:19 +02:00
@Override
2020-10-24 10:46:23 +02:00
public boolean addViewer(@NotNull Player player) {
2020-12-05 01:36:06 +01:00
synchronized (entityTypeLock) {
final boolean result = super.addViewer(player);
final PlayerConnection playerConnection = player.getPlayerConnection();
2020-05-29 02:11:41 +02:00
2020-12-05 01:36:06 +01:00
EntityMovementPacket entityMovementPacket = new EntityMovementPacket();
entityMovementPacket.entityId = getEntityId();
2019-08-10 08:44:35 +02:00
2020-12-05 01:36:06 +01:00
SpawnLivingEntityPacket spawnLivingEntityPacket = new SpawnLivingEntityPacket();
spawnLivingEntityPacket.entityId = getEntityId();
spawnLivingEntityPacket.entityUuid = getUuid();
spawnLivingEntityPacket.entityType = getEntityType().getId();
spawnLivingEntityPacket.position = getPosition();
spawnLivingEntityPacket.headPitch = 0;
2020-02-09 15:34:09 +01:00
2020-12-05 01:36:06 +01:00
playerConnection.sendPacket(entityMovementPacket);
playerConnection.sendPacket(spawnLivingEntityPacket);
playerConnection.sendPacket(getVelocityPacket());
playerConnection.sendPacket(getMetadataPacket());
2020-02-09 15:34:09 +01:00
2020-12-05 01:36:06 +01:00
// Equipments synchronization
syncEquipments(playerConnection);
2020-12-05 01:36:06 +01:00
if (hasPassenger()) {
playerConnection.sendPacket(getPassengersPacket());
}
2020-12-05 01:36:06 +01:00
return result;
}
2020-12-05 01:36:06 +01:00
}
2020-12-05 01:36:06 +01:00
@Override
public boolean removeViewer(@NotNull Player player) {
synchronized (entityTypeLock) {
return super.removeViewer(player);
}
}
/**
* Changes the entity type of this entity.
* <p>
* Works by changing the internal entity type field and by calling {@link #removeViewer(Player)}
* followed by {@link #addViewer(Player)} to all current viewers.
* <p>
* Be aware that this only change the visual of the entity, the {@link net.minestom.server.collision.BoundingBox}
* will not be modified.
*
* @param entityType the new entity type
*/
public void switchEntityType(@NotNull EntityType entityType) {
synchronized (entityTypeLock) {
this.entityType = entityType;
Set<Player> viewers = new HashSet<>(getViewers());
getViewers().forEach(this::removeViewer);
viewers.forEach(this::addViewer);
}
2019-08-10 08:44:35 +02:00
}
2020-04-09 14:25:42 +02:00
/**
* Gets the kill animation delay before vanishing the entity.
*
* @return the removal animation delay in milliseconds, 0 if not any
*/
public int getRemovalAnimationDelay() {
return removalAnimationDelay;
}
/**
* Changes the removal animation delay of the entity.
* <p>
* Testing shows that 1000 is the minimum value to display the death particles.
*
* @param removalAnimationDelay the new removal animation delay in milliseconds, 0 to remove it
*/
public void setRemovalAnimationDelay(int removalAnimationDelay) {
this.removalAnimationDelay = removalAnimationDelay;
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
2020-08-06 11:56:43 +02:00
public List<GoalSelector> getGoalSelectors() {
return goalSelectors;
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
2020-08-06 11:56:43 +02:00
public List<TargetSelector> getTargetSelectors() {
return targetSelectors;
}
@Nullable
@Override
public GoalSelector getCurrentGoalSelector() {
return currentGoalSelector;
}
@Override
public void setCurrentGoalSelector(GoalSelector currentGoalSelector) {
this.currentGoalSelector = currentGoalSelector;
}
2020-08-06 11:56:43 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the entity target.
2020-08-06 11:56:43 +02:00
*
* @return the entity target
*/
2020-10-24 11:19:54 +02:00
@Nullable
2020-08-06 11:56:43 +02:00
public Entity getTarget() {
return target;
}
/**
2020-10-29 22:52:07 +01:00
* Changes the entity target.
2020-08-06 11:56:43 +02:00
*
* @param target the new entity target
*/
2020-10-24 11:19:54 +02:00
public void setTarget(@NotNull Entity target) {
2020-08-06 11:56:43 +02:00
this.target = target;
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getItemInMainHand() {
return mainHandItem;
}
@Override
2020-10-24 11:19:54 +02:00
public void setItemInMainHand(@NotNull ItemStack itemStack) {
this.mainHandItem = itemStack;
syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getItemInOffHand() {
return offHandItem;
}
@Override
2020-10-24 11:19:54 +02:00
public void setItemInOffHand(@NotNull ItemStack itemStack) {
this.offHandItem = itemStack;
syncEquipment(EntityEquipmentPacket.Slot.OFF_HAND);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getHelmet() {
return helmet;
}
@Override
2020-10-24 11:19:54 +02:00
public void setHelmet(@NotNull ItemStack itemStack) {
2020-05-06 22:42:04 +02:00
this.helmet = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.HELMET);
syncEquipment(EntityEquipmentPacket.Slot.HELMET);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getChestplate() {
return chestplate;
}
@Override
2020-10-24 11:19:54 +02:00
public void setChestplate(@NotNull ItemStack itemStack) {
2020-05-06 22:42:04 +02:00
this.chestplate = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE);
syncEquipment(EntityEquipmentPacket.Slot.CHESTPLATE);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getLeggings() {
return leggings;
}
@Override
2020-10-24 11:19:54 +02:00
public void setLeggings(@NotNull ItemStack itemStack) {
2020-05-06 22:42:04 +02:00
this.leggings = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS);
syncEquipment(EntityEquipmentPacket.Slot.LEGGINGS);
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public ItemStack getBoots() {
return boots;
}
@Override
2020-10-24 11:19:54 +02:00
public void setBoots(@NotNull ItemStack itemStack) {
2020-05-06 22:42:04 +02:00
this.boots = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS);
syncEquipment(EntityEquipmentPacket.Slot.BOOTS);
}
2020-05-28 19:15:55 +02:00
/**
2020-10-15 21:16:31 +02:00
* Calls a {@link EntityAttackEvent} with this entity as the source and {@code target} as the target.
2020-05-28 19:15:55 +02:00
*
2020-06-03 15:17:34 +02:00
* @param target the entity target
* @param swingHand true to swing the entity main hand, false otherwise
2020-05-28 19:15:55 +02:00
*/
2020-10-24 11:19:54 +02:00
public void attack(@NotNull Entity target, boolean swingHand) {
2020-06-03 15:17:34 +02:00
if (swingHand)
swingMainHand();
2020-05-28 19:15:55 +02:00
EntityAttackEvent attackEvent = new EntityAttackEvent(this, target);
callEvent(EntityAttackEvent.class, attackEvent);
}
2020-06-03 15:17:34 +02:00
/**
2020-10-15 21:16:31 +02:00
* Calls a {@link EntityAttackEvent} with this entity as the source and {@code target} as the target.
2020-06-03 15:17:34 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* This does not trigger the hand animation.
2020-06-03 15:17:34 +02:00
*
* @param target the entity target
*/
2020-10-24 11:19:54 +02:00
public void attack(@NotNull Entity target) {
2020-06-03 15:17:34 +02:00
attack(target, false);
}
@Override
public void pathFindingTick(float speed) {
2020-12-04 18:25:24 +01:00
synchronized (pathLock) {
NavigableEntity.super.pathFindingTick(speed);
}
2020-04-09 14:25:42 +02:00
}
@Override
2020-10-24 11:19:54 +02:00
public boolean setPathTo(@Nullable Position position) {
2020-12-04 18:25:24 +01:00
synchronized (pathLock) {
return NavigableEntity.super.setPathTo(position);
}
2020-08-06 13:46:30 +02:00
}
2020-10-24 11:19:54 +02:00
@Nullable
@Override
2020-08-06 13:46:30 +02:00
public Position getPathPosition() {
return pathPosition;
}
@Override
public void setPathPosition(Position pathPosition) {
this.pathPosition = pathPosition;
}
@Nullable
@Override
public IPath getPath() {
return path;
}
@Override
public void setPath(IPath path) {
this.path = path;
2020-04-09 14:25:42 +02:00
}
2020-10-24 11:19:54 +02:00
@NotNull
@Override
public PFPathingEntity getPathingEntity() {
return pathingEntity;
}
@Nullable
@Override
public HydrazinePathFinder getPathFinder() {
return pathFinder;
}
2020-07-31 21:02:37 +02:00
@NotNull
@Override
public Entity getNavigableEntity() {
return this;
2020-07-31 21:02:37 +02:00
}
2020-10-24 11:19:54 +02:00
private ItemStack getEquipmentItem(@NotNull ItemStack itemStack, @NotNull ArmorEquipEvent.ArmorSlot armorSlot) {
2020-05-14 18:59:01 +02:00
ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(this, itemStack, armorSlot);
2020-05-06 22:42:04 +02:00
callEvent(ArmorEquipEvent.class, armorEquipEvent);
return armorEquipEvent.getArmorItem();
}
2019-08-10 08:44:35 +02:00
}