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

463 lines
14 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;
import net.minestom.server.MinecraftServer;
2020-07-24 19:31:15 +02:00
import net.minestom.server.attribute.Attribute;
2020-08-06 11:56:43 +02:00
import net.minestom.server.entity.ai.GoalSelector;
import net.minestom.server.entity.ai.TargetSelector;
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-08-06 12:33:45 +02:00
import net.minestom.server.instance.Chunk;
2020-07-24 02:31:10 +02:00
import net.minestom.server.instance.Instance;
2020-08-06 12:33:45 +02:00
import net.minestom.server.instance.WorldBorder;
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.EntityPacket;
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;
import net.minestom.server.utils.Vector;
import net.minestom.server.utils.chunk.ChunkUtils;
2020-05-15 18:03:28 +02:00
import net.minestom.server.utils.time.TimeUnit;
2020-07-24 19:31:15 +02:00
import net.minestom.server.utils.validate.Check;
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;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
2020-08-06 11:56:43 +02:00
import java.util.function.Supplier;
2020-04-09 14:25:42 +02:00
public abstract class EntityCreature extends LivingEntity {
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;
// Equipments
private ItemStack mainHandItem;
private ItemStack offHandItem;
private ItemStack helmet;
private ItemStack chestplate;
private ItemStack leggings;
private ItemStack boots;
2020-09-24 01:50:25 +02:00
private final ReentrantLock pathLock = new ReentrantLock();
2020-04-09 14:25:42 +02:00
public EntityCreature(EntityType entityType, 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
}
2019-08-24 20:34:01 +02:00
@Override
public void update(long time) {
2020-08-06 11:56:43 +02:00
2020-08-06 18:32:56 +02:00
if (getInstance() == null) {
return;
}
2020-08-09 14:13:01 +02:00
// Goal selectors
2020-08-06 11:56:43 +02:00
{
// Supplier used to get the next goal selector which should start
// (null if not found)
final Supplier<GoalSelector> goalSelectorSupplier = () -> {
for (GoalSelector goalSelector : goalSelectors) {
final boolean start = goalSelector.shouldStart();
if (start) {
return goalSelector;
}
}
return null;
};
// true if the goal selector changed this tick
boolean newGoalSelector = false;
if (currentGoalSelector == null) {
// No goal selector, get a new one
this.currentGoalSelector = goalSelectorSupplier.get();
newGoalSelector = currentGoalSelector != null;
} else {
final boolean stop = currentGoalSelector.shouldEnd();
if (stop) {
// The current goal selector stopped, find a new one
this.currentGoalSelector.end();
this.currentGoalSelector = goalSelectorSupplier.get();
newGoalSelector = currentGoalSelector != null;
}
}
// Start the new goal selector
if (newGoalSelector) {
this.currentGoalSelector.start();
}
2020-08-09 14:13:01 +02:00
// Execute tick for the current goal selector
2020-08-06 11:56:43 +02:00
if (currentGoalSelector != null) {
currentGoalSelector.tick(time);
2020-08-06 11:56:43 +02:00
}
}
2020-04-09 14:25:42 +02:00
// Path finding
{
if (pathPosition != null) {
this.pathLock.lock();
this.path = pathFinder.updatePathFor(pathingEntity);
if (path != null) {
final float speed = getAttributeValue(Attribute.MOVEMENT_SPEED);
final Position targetPosition = pathingEntity.getTargetPosition();
moveTowards(targetPosition, speed);
} else {
if (pathPosition != null) {
this.pathPosition = null;
this.pathFinder.reset();
2020-08-09 21:56:01 +02:00
}
}
2020-08-09 21:56:01 +02:00
this.pathLock.unlock();
2020-08-07 06:36:03 +02:00
}
2020-07-24 02:49:55 +02:00
}
super.update(time);
2020-07-24 02:31:10 +02:00
}
@Override
public void setInstance(Instance instance) {
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();
// Needed for proper death animation (wait for it to finish before destroying the entity)
2020-05-15 18:03:28 +02:00
scheduleRemove(1000, TimeUnit.MILLISECOND);
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-07-24 02:31:10 +02:00
final boolean result = super.addViewer(player);
2020-05-29 02:11:41 +02:00
final PlayerConnection playerConnection = player.getPlayerConnection();
2019-08-10 08:44:35 +02:00
EntityPacket entityPacket = new EntityPacket();
entityPacket.entityId = getEntityId();
2020-02-09 15:34:09 +01:00
2020-03-29 20:58:30 +02:00
SpawnLivingEntityPacket spawnLivingEntityPacket = new SpawnLivingEntityPacket();
spawnLivingEntityPacket.entityId = getEntityId();
spawnLivingEntityPacket.entityUuid = getUuid();
spawnLivingEntityPacket.entityType = getEntityType().getId();
2020-03-29 20:58:30 +02:00
spawnLivingEntityPacket.position = getPosition();
spawnLivingEntityPacket.headPitch = 0;
2020-02-09 15:34:09 +01:00
2019-08-10 08:44:35 +02:00
playerConnection.sendPacket(entityPacket);
2020-03-29 20:58:30 +02:00
playerConnection.sendPacket(spawnLivingEntityPacket);
playerConnection.sendPacket(getVelocityPacket());
2020-02-11 16:48:06 +01:00
playerConnection.sendPacket(getMetadataPacket());
// Equipments synchronization
syncEquipments(playerConnection);
if (hasPassenger()) {
playerConnection.sendPacket(getPassengersPacket());
}
return result;
2019-08-10 08:44:35 +02:00
}
2020-04-09 14:25:42 +02:00
2020-08-06 11:56:43 +02:00
/**
2020-10-15 21:16:31 +02:00
* Gets the goal selectors of this entity.
2020-08-06 11:56:43 +02:00
*
* @return a modifiable list containing the entity goal selectors
*/
2020-10-24 11:19:54 +02:00
@NotNull
2020-08-06 11:56:43 +02:00
public List<GoalSelector> getGoalSelectors() {
return goalSelectors;
}
/**
2020-10-15 21:16:31 +02:00
* Gets the target selectors of this entity.
2020-08-06 11:56:43 +02:00
*
* @return a modifiable list containing the entity target selectors
*/
2020-10-24 11:19:54 +02:00
@NotNull
2020-08-06 11:56:43 +02:00
public List<TargetSelector> getTargetSelectors() {
return targetSelectors;
}
/**
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;
}
/**
* 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);
}
2020-04-09 14:25:42 +02:00
public void jump(float height) {
// FIXME magic value
final Vector velocity = new Vector(0, height * 2.5f, 0);
setVelocity(velocity);
2020-04-09 14:25:42 +02:00
}
2020-07-24 02:49:55 +02:00
/**
2020-10-15 21:16:31 +02:00
* Retrieves the path to {@code position} and ask the entity to follow the path.
2020-07-24 02:49:55 +02:00
* <p>
2020-10-15 21:16:31 +02:00
* Can be set to null to reset the pathfinder.
2020-07-24 16:11:48 +02:00
* <p>
2020-07-24 02:49:55 +02:00
* The position is cloned, if you want the entity to continually follow this position object
2020-10-15 21:16:31 +02:00
* you need to call this when you want the path to update.
2020-07-24 02:49:55 +02:00
*
2020-07-24 16:11:48 +02:00
* @param position the position to find the path to, null to reset the pathfinder
2020-08-06 12:33:45 +02:00
* @return true if a path has been found
2020-07-24 02:49:55 +02:00
*/
2020-10-24 11:19:54 +02:00
public boolean setPathTo(@Nullable Position position) {
if (position != null && getPathPosition() != null && position.isSimilar(getPathPosition())) {
// Tried to set path to the same target position
return false;
}
if (pathFinder == null) {
// Unexpected error
return false;
}
2020-08-09 14:13:01 +02:00
this.pathLock.lock();
this.pathFinder.reset();
2020-07-24 16:11:48 +02:00
if (position == null) {
2020-08-09 21:56:01 +02:00
this.pathLock.unlock();
2020-08-06 12:33:45 +02:00
return false;
}
// Can't path outside of the world border
final WorldBorder worldBorder = instance.getWorldBorder();
if (!worldBorder.isInside(position)) {
2020-08-09 21:56:01 +02:00
this.pathLock.unlock();
2020-08-06 12:33:45 +02:00
return false;
2020-07-24 16:11:48 +02:00
}
2020-08-06 12:33:45 +02:00
// Can't path in an unloaded chunk
final Chunk chunk = instance.getChunkAt(position);
if (!ChunkUtils.isLoaded(chunk)) {
2020-08-09 21:56:01 +02:00
this.pathLock.unlock();
2020-08-06 12:33:45 +02:00
return false;
}
2020-08-09 21:56:01 +02:00
final Position targetPosition = position.clone();
2020-08-07 06:36:03 +02:00
2020-08-09 21:56:01 +02:00
this.path = pathFinder.initiatePathTo(position.getX(), position.getY(), position.getZ());
2020-08-09 14:13:01 +02:00
this.pathLock.unlock();
2020-08-06 13:46:30 +02:00
final boolean success = path != null;
2020-08-09 21:56:01 +02:00
this.pathPosition = success ? targetPosition : null;
2020-08-06 13:46:30 +02:00
return success;
}
/**
2020-10-15 21:16:31 +02:00
* Gets the target pathfinder position.
2020-08-06 13:46:30 +02:00
*
* @return the target pathfinder position, null if there is no one
*/
2020-10-24 11:19:54 +02:00
@Nullable
2020-08-06 13:46:30 +02:00
public Position getPathPosition() {
return pathPosition;
}
2020-05-24 19:59:50 +02:00
/**
* Used to move the entity toward {@code direction} in the X and Z axis
2020-05-24 19:59:50 +02:00
* Gravity is still applied but the entity will not attempt to jump
2020-07-31 21:02:37 +02:00
* Also update the yaw/pitch of the entity to look along 'direction'
2020-05-24 19:59:50 +02:00
*
* @param direction the targeted position
* @param speed define how far the entity will move
*/
2020-10-24 11:19:54 +02:00
public void moveTowards(@NotNull Position direction, float speed) {
2020-07-24 19:31:15 +02:00
Check.notNull(direction, "The direction cannot be null");
2020-07-29 00:31:45 +02:00
final float currentX = position.getX();
final float currentZ = position.getZ();
final float targetX = direction.getX();
final float targetZ = direction.getZ();
final float dz = targetZ - currentZ;
final float dx = targetX - currentX;
// the purpose of these few lines is to slow down entities when they reach their destination
float distSquared = dx * dx + dz * dz;
if (speed > distSquared) {
speed = distSquared;
}
2020-07-27 02:28:03 +02:00
final float radians = (float) Math.atan2(dz, dx);
2020-07-29 00:31:45 +02:00
final float speedX = (float) (Math.cos(radians) * speed);
final float speedZ = (float) (Math.sin(radians) * speed);
2020-07-27 02:28:03 +02:00
2020-07-31 21:02:37 +02:00
lookAlong(dx, direction.getY(), dz);
// TODO: is a hard set an issue if there are other external forces at play?
final float tps = MinecraftServer.TICK_PER_SECOND;
velocity.setX(speedX * tps);
velocity.setZ(speedZ * tps);
2020-04-09 14:25:42 +02:00
}
/**
2020-10-15 21:16:31 +02:00
* Gets the pathing entity.
* <p>
2020-10-15 21:16:31 +02:00
* Used by the pathfinder.
*
* @return the pathing entity
*/
2020-10-24 11:19:54 +02:00
@NotNull
public PFPathingEntity getPathingEntity() {
return pathingEntity;
}
2020-07-31 21:02:37 +02:00
private void lookAlong(float dx, float dy, float dz) {
final float horizontalAngle = (float) Math.atan2(dz, dx);
final float yaw = (float) (horizontalAngle * (180.0 / Math.PI)) - 90;
final float pitch = (float) Math.atan2(dy, Math.max(Math.abs(dx), Math.abs(dz)));
getPosition().setYaw(yaw);
getPosition().setPitch(pitch);
}
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
}