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;
|
|
|
|
import com.extollit.gaming.ai.path.model.PathObject;
|
2020-07-24 19:31:15 +02:00
|
|
|
import net.minestom.server.attribute.Attribute;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.collision.CollisionUtils;
|
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;
|
2020-05-07 15:46:21 +02:00
|
|
|
import net.minestom.server.event.item.ArmorEquipEvent;
|
2020-07-24 02:31:10 +02:00
|
|
|
import net.minestom.server.instance.Instance;
|
2020-05-06 22:35:32 +02:00
|
|
|
import net.minestom.server.item.ItemStack;
|
2020-04-24 03:25:58 +02:00
|
|
|
import net.minestom.server.network.packet.server.play.*;
|
|
|
|
import net.minestom.server.network.player.PlayerConnection;
|
|
|
|
import net.minestom.server.utils.Position;
|
|
|
|
import net.minestom.server.utils.Vector;
|
2020-05-25 13:46:48 +02:00
|
|
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
2020-05-23 04:20:01 +02:00
|
|
|
import net.minestom.server.utils.item.ItemStackUtils;
|
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;
|
2019-08-10 08:44:35 +02:00
|
|
|
|
2020-04-09 14:25:42 +02:00
|
|
|
public abstract class EntityCreature extends LivingEntity {
|
2020-03-29 20:58:30 +02:00
|
|
|
|
2020-07-24 02:31:10 +02:00
|
|
|
private PFPathingEntity pathingEntity = new PFPathingEntity(this);
|
|
|
|
private HydrazinePathFinder pathFinder;
|
|
|
|
private PathObject path;
|
2020-03-29 20:58:30 +02:00
|
|
|
|
2020-05-06 22:35:32 +02:00
|
|
|
// Equipments
|
|
|
|
private ItemStack mainHandItem;
|
|
|
|
private ItemStack offHandItem;
|
|
|
|
|
|
|
|
private ItemStack helmet;
|
|
|
|
private ItemStack chestplate;
|
|
|
|
private ItemStack leggings;
|
|
|
|
private ItemStack boots;
|
|
|
|
|
|
|
|
|
2020-04-09 14:25:42 +02:00
|
|
|
public EntityCreature(EntityType entityType, Position spawnPosition) {
|
2020-05-25 01:12:12 +02:00
|
|
|
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
|
2020-06-01 00:51:31 +02:00
|
|
|
public void update(long time) {
|
|
|
|
super.update(time);
|
2020-04-09 14:25:42 +02:00
|
|
|
|
|
|
|
// Path finding
|
2020-07-24 02:31:10 +02:00
|
|
|
path = pathFinder.update();
|
2020-07-24 02:49:55 +02:00
|
|
|
if (path != null) {
|
2020-07-24 02:31:10 +02:00
|
|
|
path.update(pathingEntity);
|
2020-07-24 02:49:55 +02:00
|
|
|
if (path.done()) {
|
|
|
|
pathFinder.reset();
|
2020-07-24 19:31:15 +02:00
|
|
|
} else {
|
|
|
|
final float speed = getAttributeValue(Attribute.MOVEMENT_SPEED);
|
|
|
|
Position targetPosition = pathingEntity.getTargetPosition();
|
2020-07-29 00:31:45 +02:00
|
|
|
//targetPosition = new Position(-5.5f, 40f, -5.5f);
|
2020-07-29 20:13:17 +02:00
|
|
|
//System.out.println("target: " + targetPosition + " : " + (System.currentTimeMillis() - time));
|
2020-07-29 00:31:45 +02:00
|
|
|
//System.out.println("current: " + getPosition());
|
2020-07-24 19:31:15 +02:00
|
|
|
moveTowards(targetPosition, speed);
|
2020-07-24 02:49:55 +02:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
|
|
|
* @param x X movement offset
|
|
|
|
* @param y Y movement offset
|
|
|
|
* @param z Z movement offset
|
|
|
|
* @param updateView should the entity move its head toward the position?
|
|
|
|
*/
|
2019-08-30 01:17:46 +02:00
|
|
|
public void move(float x, float y, float z, boolean updateView) {
|
2020-07-22 17:39:48 +02:00
|
|
|
final Position position = getPosition();
|
2020-05-02 23:34:09 +02:00
|
|
|
Position newPosition = new Position();
|
2020-04-22 19:09:57 +02:00
|
|
|
// Calculate collisions boxes
|
2020-05-02 23:34:09 +02:00
|
|
|
onGround = CollisionUtils.handlePhysics(this, new Vector(x, y, z), newPosition, new Vector());
|
2020-04-22 19:09:57 +02:00
|
|
|
// Refresh target position
|
2020-07-22 17:39:48 +02:00
|
|
|
final float newX = newPosition.getX();
|
|
|
|
final float newY = newPosition.getY();
|
|
|
|
final float newZ = newPosition.getZ();
|
2019-08-11 13:57:23 +02:00
|
|
|
|
2020-07-29 00:31:45 +02:00
|
|
|
// Creatures cannot move in unloaded chunk
|
2019-08-27 20:49:11 +02:00
|
|
|
if (ChunkUtils.isChunkUnloaded(getInstance(), newX, newZ))
|
2019-08-11 13:57:23 +02:00
|
|
|
return;
|
|
|
|
|
2020-07-22 17:39:48 +02:00
|
|
|
final float lastYaw = position.getYaw();
|
|
|
|
final float radians = (float) Math.atan2(newZ - position.getZ(), newX - position.getX());
|
2019-08-29 02:15:52 +02:00
|
|
|
|
2020-07-22 17:39:48 +02:00
|
|
|
final float yaw = (float) (radians * (180.0 / Math.PI)) - 90;
|
|
|
|
final float pitch = position.getPitch(); // TODO
|
2019-08-29 02:15:52 +02:00
|
|
|
|
2020-06-01 18:57:16 +02:00
|
|
|
final short deltaX = (short) ((newX * 32 - position.getX() * 32) * 128);
|
|
|
|
final short deltaY = (short) ((newY * 32 - position.getY() * 32) * 128);
|
|
|
|
final short deltaZ = (short) ((newZ * 32 - position.getZ() * 32) * 128);
|
2020-02-11 16:48:06 +01:00
|
|
|
|
2019-08-30 01:17:46 +02:00
|
|
|
if (updateView) {
|
2020-02-11 16:48:06 +01:00
|
|
|
EntityPositionAndRotationPacket entityPositionAndRotationPacket = new EntityPositionAndRotationPacket();
|
|
|
|
entityPositionAndRotationPacket.entityId = getEntityId();
|
|
|
|
entityPositionAndRotationPacket.deltaX = deltaX;
|
|
|
|
entityPositionAndRotationPacket.deltaY = deltaY;
|
|
|
|
entityPositionAndRotationPacket.deltaZ = deltaZ;
|
|
|
|
entityPositionAndRotationPacket.yaw = yaw;
|
|
|
|
entityPositionAndRotationPacket.pitch = pitch;
|
|
|
|
entityPositionAndRotationPacket.onGround = isOnGround();
|
|
|
|
sendPacketToViewers(entityPositionAndRotationPacket);
|
2019-08-30 01:17:46 +02:00
|
|
|
} else {
|
2020-02-11 16:48:06 +01:00
|
|
|
EntityPositionPacket entityPositionPacket = new EntityPositionPacket();
|
|
|
|
entityPositionPacket.entityId = getEntityId();
|
|
|
|
entityPositionPacket.deltaX = deltaX;
|
|
|
|
entityPositionPacket.deltaY = deltaY;
|
|
|
|
entityPositionPacket.deltaZ = deltaZ;
|
|
|
|
entityPositionPacket.onGround = isOnGround();
|
|
|
|
sendPacketToViewers(entityPositionPacket);
|
2019-08-30 01:17:46 +02:00
|
|
|
}
|
2019-08-29 02:15:52 +02:00
|
|
|
|
|
|
|
if (lastYaw != yaw) {
|
2020-05-27 20:55:33 +02:00
|
|
|
setView(yaw, pitch);
|
2019-08-29 02:15:52 +02:00
|
|
|
}
|
2019-08-11 13:57:23 +02:00
|
|
|
|
|
|
|
refreshPosition(newX, newY, newZ);
|
2019-08-29 02:15:52 +02:00
|
|
|
}
|
|
|
|
|
2020-05-26 22:53:58 +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-05-23 17:57:56 +02:00
|
|
|
public boolean addViewer(Player player) {
|
2020-07-24 02:31:10 +02:00
|
|
|
final boolean result = super.addViewer(player);
|
2020-05-29 02:11:41 +02:00
|
|
|
if (!result)
|
|
|
|
return false;
|
|
|
|
|
2019-08-10 08:44:35 +02:00
|
|
|
PlayerConnection playerConnection = player.getPlayerConnection();
|
|
|
|
|
|
|
|
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();
|
|
|
|
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);
|
2020-05-24 19:22:58 +02:00
|
|
|
playerConnection.sendPacket(getVelocityPacket());
|
2020-02-11 16:48:06 +01:00
|
|
|
playerConnection.sendPacket(getMetadataPacket());
|
2020-05-24 19:22:58 +02:00
|
|
|
|
2020-05-25 01:12:12 +02:00
|
|
|
// Equipments synchronization
|
2020-05-30 21:56:12 +02:00
|
|
|
syncEquipments(playerConnection);
|
2020-05-25 01:12:12 +02:00
|
|
|
|
2020-05-24 19:22:58 +02:00
|
|
|
if (hasPassenger()) {
|
|
|
|
playerConnection.sendPacket(getPassengersPacket());
|
|
|
|
}
|
|
|
|
|
2020-05-23 17:57:56 +02:00
|
|
|
return result;
|
2019-08-10 08:44:35 +02:00
|
|
|
}
|
2020-04-09 14:25:42 +02:00
|
|
|
|
2020-05-06 22:35:32 +02:00
|
|
|
@Override
|
|
|
|
public ItemStack getItemInMainHand() {
|
|
|
|
return mainHandItem;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInMainHand(ItemStack itemStack) {
|
2020-05-23 04:20:01 +02:00
|
|
|
this.mainHandItem = ItemStackUtils.notNull(itemStack);
|
2020-05-06 22:35:32 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getItemInOffHand() {
|
|
|
|
return offHandItem;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setItemInOffHand(ItemStack itemStack) {
|
2020-05-23 04:20:01 +02:00
|
|
|
this.offHandItem = ItemStackUtils.notNull(itemStack);
|
2020-05-06 22:35:32 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.OFF_HAND);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getHelmet() {
|
|
|
|
return helmet;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setHelmet(ItemStack itemStack) {
|
2020-05-06 22:42:04 +02:00
|
|
|
this.helmet = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.HELMET);
|
2020-05-06 22:35:32 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.HELMET);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getChestplate() {
|
|
|
|
return chestplate;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setChestplate(ItemStack itemStack) {
|
2020-05-06 22:42:04 +02:00
|
|
|
this.chestplate = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE);
|
2020-05-06 22:35:32 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.CHESTPLATE);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getLeggings() {
|
|
|
|
return leggings;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setLeggings(ItemStack itemStack) {
|
2020-05-06 22:42:04 +02:00
|
|
|
this.leggings = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS);
|
2020-05-06 22:35:32 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.LEGGINGS);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack getBoots() {
|
|
|
|
return boots;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setBoots(ItemStack itemStack) {
|
2020-05-06 22:42:04 +02:00
|
|
|
this.boots = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS);
|
2020-05-06 22:35:32 +02:00
|
|
|
syncEquipment(EntityEquipmentPacket.Slot.BOOTS);
|
|
|
|
}
|
|
|
|
|
2020-05-28 19:15:55 +02:00
|
|
|
/**
|
2020-06-03 15:17:34 +02:00
|
|
|
* Call 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-06-03 15:17:34 +02:00
|
|
|
public void attack(Entity target, boolean swingHand) {
|
|
|
|
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
|
|
|
/**
|
|
|
|
* Call a {@link EntityAttackEvent} with this entity as the source and {@code target} as the target.
|
|
|
|
* <p>
|
|
|
|
* This does not trigger the hand animation
|
|
|
|
*
|
|
|
|
* @param target the entity target
|
|
|
|
*/
|
|
|
|
public void attack(Entity target) {
|
|
|
|
attack(target, false);
|
|
|
|
}
|
|
|
|
|
2020-04-09 14:25:42 +02:00
|
|
|
public void jump(float height) {
|
|
|
|
// FIXME magic value
|
2020-07-29 20:13:17 +02:00
|
|
|
final Vector velocity = new Vector(0, height * 2.5f, 0);
|
2020-05-02 23:34:09 +02:00
|
|
|
setVelocity(velocity);
|
2020-04-09 14:25:42 +02:00
|
|
|
}
|
|
|
|
|
2020-07-24 02:49:55 +02:00
|
|
|
/**
|
|
|
|
* Retrieve the path to {@code position} and ask the entity to follow the path
|
|
|
|
* <p>
|
2020-07-24 16:11:48 +02:00
|
|
|
* Can be set to null to reset the pathfinder
|
|
|
|
* <p>
|
2020-07-24 02:49:55 +02:00
|
|
|
* The position is cloned, if you want the entity to continually follow this position object
|
|
|
|
* you need to call this when you want the path to update
|
|
|
|
*
|
2020-07-24 16:11:48 +02:00
|
|
|
* @param position the position to find the path to, null to reset the pathfinder
|
2020-07-24 02:49:55 +02:00
|
|
|
*/
|
2020-04-25 23:51:45 +02:00
|
|
|
public void setPathTo(Position position) {
|
2020-07-24 16:11:48 +02:00
|
|
|
if (position == null) {
|
|
|
|
this.pathFinder.reset();
|
|
|
|
return;
|
|
|
|
}
|
2020-07-24 02:31:10 +02:00
|
|
|
position = position.clone();
|
|
|
|
this.path = pathFinder.initiatePathTo(position.getX(), position.getY(), position.getZ());
|
2020-04-25 23:51:45 +02:00
|
|
|
}
|
|
|
|
|
2020-05-24 19:59:50 +02:00
|
|
|
/**
|
2020-05-30 19:47:47 +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
|
|
|
|
*
|
|
|
|
* @param direction the targeted position
|
|
|
|
* @param speed define how far the entity will move
|
|
|
|
*/
|
2020-04-09 14:25:42 +02:00
|
|
|
public void moveTowards(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();
|
2020-07-27 02:28:03 +02:00
|
|
|
|
2020-07-29 00:31:45 +02:00
|
|
|
final float radians = (float) Math.atan2(targetZ - currentZ, targetX - currentX);
|
|
|
|
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-29 00:31:45 +02:00
|
|
|
move(speedX, 0, speedZ, true);
|
2020-04-09 14:25:42 +02:00
|
|
|
}
|
|
|
|
|
2020-05-06 22:42:04 +02:00
|
|
|
private ItemStack getEquipmentItem(ItemStack itemStack, ArmorEquipEvent.ArmorSlot armorSlot) {
|
2020-05-23 04:20:01 +02:00
|
|
|
itemStack = ItemStackUtils.notNull(itemStack);
|
|
|
|
|
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
|
|
|
}
|