mirror of
https://github.com/Minestom/Minestom.git
synced 2025-03-13 07:09:51 +01:00
787 lines
26 KiB
Java
787 lines
26 KiB
Java
package net.minestom.server.entity;
|
|
|
|
import net.minestom.server.attribute.Attribute;
|
|
import net.minestom.server.attribute.AttributeInstance;
|
|
import net.minestom.server.attribute.Attributes;
|
|
import net.minestom.server.collision.BoundingBox;
|
|
import net.minestom.server.entity.damage.DamageType;
|
|
import net.minestom.server.entity.metadata.LivingEntityMeta;
|
|
import net.minestom.server.event.entity.EntityDamageEvent;
|
|
import net.minestom.server.event.entity.EntityDeathEvent;
|
|
import net.minestom.server.event.entity.EntityFireEvent;
|
|
import net.minestom.server.event.item.ArmorEquipEvent;
|
|
import net.minestom.server.event.item.PickupItemEvent;
|
|
import net.minestom.server.instance.Chunk;
|
|
import net.minestom.server.instance.block.Block;
|
|
import net.minestom.server.inventory.EquipmentHandler;
|
|
import net.minestom.server.item.ItemStack;
|
|
import net.minestom.server.network.ConnectionState;
|
|
import net.minestom.server.network.packet.server.play.*;
|
|
import net.minestom.server.network.player.PlayerConnection;
|
|
import net.minestom.server.scoreboard.Team;
|
|
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;
|
|
import net.minestom.server.utils.time.UpdateOption;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
//TODO: Default attributes registration (and limitation ?)
|
|
public class LivingEntity extends Entity implements EquipmentHandler {
|
|
|
|
// Item pickup
|
|
protected boolean canPickupItem;
|
|
protected UpdateOption itemPickupCooldown = new UpdateOption(5, TimeUnit.TICK);
|
|
private long lastItemPickupCheckTime;
|
|
|
|
protected boolean isDead;
|
|
|
|
protected DamageType lastDamageSource;
|
|
|
|
// Bounding box used for items' pickup (see LivingEntity#setBoundingBox)
|
|
protected BoundingBox expandedBoundingBox;
|
|
|
|
private final Map<String, AttributeInstance> attributeModifiers = new ConcurrentHashMap<>(Attribute.values().length);
|
|
|
|
// Abilities
|
|
protected boolean invulnerable;
|
|
|
|
/**
|
|
* Time at which this entity must be extinguished
|
|
*/
|
|
private long fireExtinguishTime;
|
|
|
|
/**
|
|
* Last time the fire damage was applied
|
|
*/
|
|
private long lastFireDamageTime;
|
|
|
|
/**
|
|
* Period, in ms, between two fire damage applications
|
|
*/
|
|
private long fireDamagePeriod = 1000L;
|
|
|
|
private Team team;
|
|
|
|
private int arrowCount;
|
|
private float health = 1F;
|
|
|
|
// Equipments
|
|
private ItemStack mainHandItem;
|
|
private ItemStack offHandItem;
|
|
|
|
private ItemStack helmet;
|
|
private ItemStack chestplate;
|
|
private ItemStack leggings;
|
|
private ItemStack boots;
|
|
|
|
/**
|
|
* Constructor which allows to specify an UUID. Only use if you know what you are doing!
|
|
*/
|
|
public LivingEntity(@NotNull EntityType entityType, @NotNull UUID uuid) {
|
|
this(entityType, uuid, new Position());
|
|
setupAttributes();
|
|
setGravity(0.02f, 0.08f, 3.92f);
|
|
initEquipments();
|
|
}
|
|
|
|
public LivingEntity(@NotNull EntityType entityType) {
|
|
this(entityType, UUID.randomUUID());
|
|
}
|
|
|
|
/**
|
|
* Constructor which allows to specify an UUID. Only use if you know what you are doing!
|
|
*/
|
|
@Deprecated
|
|
public LivingEntity(@NotNull EntityType entityType, @NotNull UUID uuid, @NotNull Position spawnPosition) {
|
|
super(entityType, uuid, spawnPosition);
|
|
setupAttributes();
|
|
setGravity(0.02f, 0.08f, 3.92f);
|
|
initEquipments();
|
|
}
|
|
|
|
@Deprecated
|
|
public LivingEntity(@NotNull EntityType entityType, @NotNull Position spawnPosition) {
|
|
this(entityType, UUID.randomUUID(), spawnPosition);
|
|
}
|
|
|
|
private void initEquipments() {
|
|
this.mainHandItem = ItemStack.getAirItem();
|
|
this.offHandItem = ItemStack.getAirItem();
|
|
|
|
this.helmet = ItemStack.getAirItem();
|
|
this.chestplate = ItemStack.getAirItem();
|
|
this.leggings = ItemStack.getAirItem();
|
|
this.boots = ItemStack.getAirItem();
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getItemInMainHand() {
|
|
return mainHandItem;
|
|
}
|
|
|
|
@Override
|
|
public void setItemInMainHand(@NotNull ItemStack itemStack) {
|
|
this.mainHandItem = itemStack;
|
|
syncEquipment(EntityEquipmentPacket.Slot.MAIN_HAND);
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getItemInOffHand() {
|
|
return offHandItem;
|
|
}
|
|
|
|
@Override
|
|
public void setItemInOffHand(@NotNull ItemStack itemStack) {
|
|
this.offHandItem = itemStack;
|
|
syncEquipment(EntityEquipmentPacket.Slot.OFF_HAND);
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getHelmet() {
|
|
return helmet;
|
|
}
|
|
|
|
@Override
|
|
public void setHelmet(@NotNull ItemStack itemStack) {
|
|
this.helmet = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.HELMET);
|
|
syncEquipment(EntityEquipmentPacket.Slot.HELMET);
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getChestplate() {
|
|
return chestplate;
|
|
}
|
|
|
|
@Override
|
|
public void setChestplate(@NotNull ItemStack itemStack) {
|
|
this.chestplate = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.CHESTPLATE);
|
|
syncEquipment(EntityEquipmentPacket.Slot.CHESTPLATE);
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getLeggings() {
|
|
return leggings;
|
|
}
|
|
|
|
@Override
|
|
public void setLeggings(@NotNull ItemStack itemStack) {
|
|
this.leggings = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.LEGGINGS);
|
|
syncEquipment(EntityEquipmentPacket.Slot.LEGGINGS);
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public ItemStack getBoots() {
|
|
return boots;
|
|
}
|
|
|
|
@Override
|
|
public void setBoots(@NotNull ItemStack itemStack) {
|
|
this.boots = getEquipmentItem(itemStack, ArmorEquipEvent.ArmorSlot.BOOTS);
|
|
syncEquipment(EntityEquipmentPacket.Slot.BOOTS);
|
|
}
|
|
|
|
private ItemStack getEquipmentItem(@NotNull ItemStack itemStack, @NotNull ArmorEquipEvent.ArmorSlot armorSlot) {
|
|
ArmorEquipEvent armorEquipEvent = new ArmorEquipEvent(this, itemStack, armorSlot);
|
|
callEvent(ArmorEquipEvent.class, armorEquipEvent);
|
|
return armorEquipEvent.getArmorItem();
|
|
}
|
|
|
|
@Override
|
|
public void update(long time) {
|
|
if (isOnFire()) {
|
|
if (time > fireExtinguishTime) {
|
|
setOnFire(false);
|
|
} else {
|
|
if (time - lastFireDamageTime > fireDamagePeriod) {
|
|
damage(DamageType.ON_FIRE, 1.0f);
|
|
lastFireDamageTime = time;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Items picking
|
|
if (canPickupItem() && !CooldownUtils.hasCooldown(time, lastItemPickupCheckTime, itemPickupCooldown)) {
|
|
this.lastItemPickupCheckTime = time;
|
|
|
|
final Chunk chunk = getChunk(); // TODO check surrounding chunks
|
|
final Set<Entity> entities = instance.getChunkEntities(chunk);
|
|
for (Entity entity : entities) {
|
|
if (entity instanceof ItemEntity) {
|
|
|
|
// Do not pickup if not visible
|
|
if (this instanceof Player && !entity.isViewer((Player) this))
|
|
continue;
|
|
|
|
final ItemEntity itemEntity = (ItemEntity) entity;
|
|
if (!itemEntity.isPickable())
|
|
continue;
|
|
|
|
final BoundingBox itemBoundingBox = itemEntity.getBoundingBox();
|
|
if (expandedBoundingBox.intersect(itemBoundingBox)) {
|
|
if (itemEntity.shouldRemove() || itemEntity.isRemoveScheduled())
|
|
continue;
|
|
PickupItemEvent pickupItemEvent = new PickupItemEvent(this, itemEntity);
|
|
callCancellableEvent(PickupItemEvent.class, pickupItemEvent, () -> {
|
|
final ItemStack item = itemEntity.getItemStack();
|
|
|
|
CollectItemPacket collectItemPacket = new CollectItemPacket();
|
|
collectItemPacket.collectedEntityId = itemEntity.getEntityId();
|
|
collectItemPacket.collectorEntityId = getEntityId();
|
|
collectItemPacket.pickupItemCount = item.getAmount();
|
|
sendPacketToViewersAndSelf(collectItemPacket);
|
|
entity.remove();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the amount of arrows in the entity.
|
|
*
|
|
* @return the arrow count
|
|
*/
|
|
public int getArrowCount() {
|
|
return this.arrowCount;
|
|
}
|
|
|
|
/**
|
|
* Changes the amount of arrow stuck in the entity.
|
|
*
|
|
* @param arrowCount the arrow count
|
|
*/
|
|
public void setArrowCount(int arrowCount) {
|
|
this.arrowCount = arrowCount;
|
|
LivingEntityMeta meta = getLivingEntityMeta();
|
|
if (meta != null) {
|
|
meta.setArrowCount(arrowCount);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets if the entity is invulnerable.
|
|
*
|
|
* @return true if the entity is invulnerable
|
|
*/
|
|
public boolean isInvulnerable() {
|
|
return invulnerable;
|
|
}
|
|
|
|
/**
|
|
* Makes the entity vulnerable or invulnerable.
|
|
*
|
|
* @param invulnerable should the entity be invulnerable
|
|
*/
|
|
public void setInvulnerable(boolean invulnerable) {
|
|
this.invulnerable = invulnerable;
|
|
}
|
|
|
|
/**
|
|
* Kills the entity, trigger the {@link EntityDeathEvent} event.
|
|
*/
|
|
public void kill() {
|
|
refreshIsDead(true); // So the entity isn't killed over and over again
|
|
triggerStatus((byte) 3); // Start death animation status
|
|
setHealth(0);
|
|
|
|
// Reset velocity
|
|
velocity.zero();
|
|
|
|
// Remove passengers if any
|
|
if (hasPassenger()) {
|
|
getPassengers().forEach(this::removePassenger);
|
|
}
|
|
|
|
EntityDeathEvent entityDeathEvent = new EntityDeathEvent(this);
|
|
callEvent(EntityDeathEvent.class, entityDeathEvent);
|
|
}
|
|
|
|
/**
|
|
* Sets fire to this entity for a given duration.
|
|
*
|
|
* @param duration duration in ticks of the effect
|
|
*/
|
|
public void setFireForDuration(int duration) {
|
|
setFireForDuration(duration, TimeUnit.TICK);
|
|
}
|
|
|
|
/**
|
|
* Sets fire to this entity for a given duration.
|
|
*
|
|
* @param duration duration of the effect
|
|
* @param unit unit used to express the duration
|
|
* @see #setOnFire(boolean) if you want it to be permanent without any event callback
|
|
*/
|
|
public void setFireForDuration(int duration, TimeUnit unit) {
|
|
EntityFireEvent entityFireEvent = new EntityFireEvent(this, duration, unit);
|
|
|
|
// Do not start fire event if the fire needs to be removed (< 0 duration)
|
|
if (duration > 0) {
|
|
callCancellableEvent(EntityFireEvent.class, entityFireEvent, () -> {
|
|
final long fireTime = entityFireEvent.getFireTime(TimeUnit.MILLISECOND);
|
|
setOnFire(true);
|
|
fireExtinguishTime = System.currentTimeMillis() + fireTime;
|
|
});
|
|
} else {
|
|
fireExtinguishTime = System.currentTimeMillis();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Damages the entity by a value, the type of the damage also has to be specified.
|
|
*
|
|
* @param type the damage type
|
|
* @param value the amount of damage
|
|
* @return true if damage has been applied, false if it didn't
|
|
*/
|
|
public boolean damage(@NotNull DamageType type, float value) {
|
|
if (isDead())
|
|
return false;
|
|
if (isInvulnerable() || isImmune(type)) {
|
|
return false;
|
|
}
|
|
|
|
EntityDamageEvent entityDamageEvent = new EntityDamageEvent(this, type, value);
|
|
callCancellableEvent(EntityDamageEvent.class, entityDamageEvent, () -> {
|
|
// Set the last damage type since the event is not cancelled
|
|
this.lastDamageSource = entityDamageEvent.getDamageType();
|
|
|
|
float remainingDamage = entityDamageEvent.getDamage();
|
|
|
|
EntityAnimationPacket entityAnimationPacket = new EntityAnimationPacket();
|
|
entityAnimationPacket.entityId = getEntityId();
|
|
entityAnimationPacket.animation = EntityAnimationPacket.Animation.TAKE_DAMAGE;
|
|
sendPacketToViewersAndSelf(entityAnimationPacket);
|
|
|
|
// Additional hearts support
|
|
if (this instanceof Player) {
|
|
final Player player = (Player) this;
|
|
final float additionalHearts = player.getAdditionalHearts();
|
|
if (additionalHearts > 0) {
|
|
if (remainingDamage > additionalHearts) {
|
|
remainingDamage -= additionalHearts;
|
|
player.setAdditionalHearts(0);
|
|
} else {
|
|
player.setAdditionalHearts(additionalHearts - remainingDamage);
|
|
remainingDamage = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the final entity health
|
|
setHealth(getHealth() - remainingDamage);
|
|
|
|
// play damage sound
|
|
final Sound sound = type.getSound(this);
|
|
if (sound != null) {
|
|
SoundCategory soundCategory;
|
|
if (this instanceof Player) {
|
|
soundCategory = SoundCategory.PLAYERS;
|
|
} else {
|
|
// TODO: separate living entity categories
|
|
soundCategory = SoundCategory.HOSTILE;
|
|
}
|
|
|
|
SoundEffectPacket damageSoundPacket =
|
|
SoundEffectPacket.create(soundCategory, sound,
|
|
getPosition(),
|
|
1.0f, 1.0f);
|
|
sendPacketToViewersAndSelf(damageSoundPacket);
|
|
}
|
|
});
|
|
|
|
return !entityDamageEvent.isCancelled();
|
|
}
|
|
|
|
/**
|
|
* Is this entity immune to the given type of damage?
|
|
*
|
|
* @param type the type of damage
|
|
* @return true if this entity is immune to the given type of damage
|
|
*/
|
|
public boolean isImmune(@NotNull DamageType type) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Gets the entity health.
|
|
*
|
|
* @return the entity health
|
|
*/
|
|
public float getHealth() {
|
|
return this.health;
|
|
}
|
|
|
|
/**
|
|
* Changes the entity health, kill it if {@code health} is >= 0 and is not dead yet.
|
|
*
|
|
* @param health the new entity health
|
|
*/
|
|
public void setHealth(float health) {
|
|
this.health = Math.min(health, getMaxHealth());
|
|
if (this.health <= 0 && !isDead) {
|
|
kill();
|
|
}
|
|
LivingEntityMeta meta = getLivingEntityMeta();
|
|
if (meta != null) {
|
|
meta.setHealth(this.health);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the last damage source which damaged of this entity.
|
|
*
|
|
* @return the last damage source, null if not any
|
|
*/
|
|
@Nullable
|
|
public DamageType getLastDamageSource() {
|
|
return lastDamageSource;
|
|
}
|
|
|
|
/**
|
|
* Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attributes#MAX_HEALTH}.
|
|
*
|
|
* @return the entity max health
|
|
*/
|
|
public float getMaxHealth() {
|
|
return getAttributeValue(Attribute.MAX_HEALTH);
|
|
}
|
|
|
|
/**
|
|
* Sets the heal of the entity as its max health.
|
|
* <p>
|
|
* Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attributes#MAX_HEALTH}.
|
|
*/
|
|
public void heal() {
|
|
setHealth(getAttributeValue(Attribute.MAX_HEALTH));
|
|
}
|
|
|
|
/**
|
|
* Retrieves the attribute instance and its modifiers.
|
|
*
|
|
* @param attribute the attribute instance to get
|
|
* @return the attribute instance
|
|
*/
|
|
@NotNull
|
|
public AttributeInstance getAttribute(@NotNull Attribute attribute) {
|
|
return attributeModifiers.computeIfAbsent(attribute.getKey(),
|
|
s -> new AttributeInstance(attribute, this::onAttributeChanged));
|
|
}
|
|
|
|
/**
|
|
* Callback used when an attribute instance has been modified.
|
|
*
|
|
* @param attributeInstance the modified attribute instance
|
|
*/
|
|
protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) {
|
|
if (attributeInstance.getAttribute().isShared()) {
|
|
boolean self = false;
|
|
if (this instanceof Player) {
|
|
Player player = (Player) this;
|
|
PlayerConnection playerConnection = player.playerConnection;
|
|
// connection null during Player initialization (due to #super call)
|
|
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
|
|
}
|
|
if (self) {
|
|
sendPacketToViewersAndSelf(getPropertiesPacket());
|
|
} else {
|
|
sendPacketToViewers(getPropertiesPacket());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the attribute value.
|
|
*
|
|
* @param attribute the attribute value to get
|
|
* @return the attribute value
|
|
*/
|
|
public float getAttributeValue(@NotNull Attribute attribute) {
|
|
AttributeInstance instance = attributeModifiers.get(attribute.getKey());
|
|
return (instance != null) ? instance.getValue() : attribute.getDefaultValue();
|
|
}
|
|
|
|
/**
|
|
* Gets if the entity is dead or not.
|
|
*
|
|
* @return true if the entity is dead
|
|
*/
|
|
public boolean isDead() {
|
|
return isDead;
|
|
}
|
|
|
|
/**
|
|
* Gets if the entity is able to pickup items.
|
|
*
|
|
* @return true if the entity is able to pickup items
|
|
*/
|
|
public boolean canPickupItem() {
|
|
return canPickupItem;
|
|
}
|
|
|
|
/**
|
|
* When set to false, the entity will not be able to pick {@link ItemEntity} on the ground.
|
|
*
|
|
* @param canPickupItem can the entity pickup item
|
|
*/
|
|
public void setCanPickupItem(boolean canPickupItem) {
|
|
this.canPickupItem = canPickupItem;
|
|
}
|
|
|
|
@Override
|
|
public boolean addViewer0(@NotNull Player player) {
|
|
if (!super.addViewer0(player)) {
|
|
return false;
|
|
}
|
|
final PlayerConnection playerConnection = player.getPlayerConnection();
|
|
playerConnection.sendPacket(getEquipmentsPacket());
|
|
playerConnection.sendPacket(getPropertiesPacket());
|
|
|
|
if (getTeam() != null){
|
|
playerConnection.sendPacket(getTeam().createTeamsCreationPacket());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void setBoundingBox(double x, double y, double z) {
|
|
super.setBoundingBox(x, y, z);
|
|
this.expandedBoundingBox = getBoundingBox().expand(1, 0.5f, 1);
|
|
}
|
|
|
|
/**
|
|
* Sends a {@link EntityAnimationPacket} to swing the main hand
|
|
* (can be used for attack animation).
|
|
*/
|
|
public void swingMainHand() {
|
|
EntityAnimationPacket animationPacket = new EntityAnimationPacket();
|
|
animationPacket.entityId = getEntityId();
|
|
animationPacket.animation = EntityAnimationPacket.Animation.SWING_MAIN_ARM;
|
|
sendPacketToViewers(animationPacket);
|
|
}
|
|
|
|
/**
|
|
* Sends a {@link EntityAnimationPacket} to swing the off hand
|
|
* (can be used for attack animation).
|
|
*/
|
|
public void swingOffHand() {
|
|
EntityAnimationPacket animationPacket = new EntityAnimationPacket();
|
|
animationPacket.entityId = getEntityId();
|
|
animationPacket.animation = EntityAnimationPacket.Animation.SWING_OFF_HAND;
|
|
sendPacketToViewers(animationPacket);
|
|
}
|
|
|
|
public void refreshActiveHand(boolean isHandActive, boolean offHand, boolean riptideSpinAttack) {
|
|
LivingEntityMeta meta = getLivingEntityMeta();
|
|
if (meta != null) {
|
|
meta.setNotifyAboutChanges(false);
|
|
meta.setHandActive(isHandActive);
|
|
meta.setActiveHand(offHand ? Player.Hand.OFF : Player.Hand.MAIN);
|
|
meta.setInRiptideSpinAttack(riptideSpinAttack);
|
|
meta.setNotifyAboutChanges(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used to change the {@code isDead} internal field.
|
|
*
|
|
* @param isDead the new field value
|
|
*/
|
|
protected void refreshIsDead(boolean isDead) {
|
|
this.isDead = isDead;
|
|
}
|
|
|
|
/**
|
|
* Gets an {@link EntityPropertiesPacket} for this entity with all of its attributes values.
|
|
*
|
|
* @return an {@link EntityPropertiesPacket} linked to this entity
|
|
*/
|
|
@NotNull
|
|
protected EntityPropertiesPacket getPropertiesPacket() {
|
|
// Get all the attributes which should be sent to the client
|
|
final AttributeInstance[] instances = attributeModifiers.values().stream()
|
|
.filter(i -> i.getAttribute().isShared())
|
|
.toArray(AttributeInstance[]::new);
|
|
|
|
|
|
EntityPropertiesPacket propertiesPacket = new EntityPropertiesPacket();
|
|
propertiesPacket.entityId = getEntityId();
|
|
|
|
EntityPropertiesPacket.Property[] properties = new EntityPropertiesPacket.Property[instances.length];
|
|
for (int i = 0; i < properties.length; ++i) {
|
|
EntityPropertiesPacket.Property property = new EntityPropertiesPacket.Property();
|
|
|
|
final float value = instances[i].getBaseValue();
|
|
|
|
property.instance = instances[i];
|
|
property.attribute = instances[i].getAttribute();
|
|
property.value = value;
|
|
|
|
properties[i] = property;
|
|
}
|
|
|
|
propertiesPacket.properties = properties;
|
|
return propertiesPacket;
|
|
}
|
|
|
|
/**
|
|
* Sets all the attributes to {@link Attribute#getDefaultValue()}
|
|
*/
|
|
private void setupAttributes() {
|
|
for (Attribute attribute : Attribute.values()) {
|
|
final AttributeInstance attributeInstance = new AttributeInstance(attribute, this::onAttributeChanged);
|
|
this.attributeModifiers.put(attribute.getKey(), attributeInstance);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void handleVoid() {
|
|
// Kill if in void
|
|
if (getInstance().isInVoid(this.position)) {
|
|
damage(DamageType.VOID, 10f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the time in ms between two fire damage applications.
|
|
*
|
|
* @return the time in ms
|
|
* @see #setFireDamagePeriod(long, TimeUnit)
|
|
*/
|
|
public long getFireDamagePeriod() {
|
|
return fireDamagePeriod;
|
|
}
|
|
|
|
/**
|
|
* Changes the delay between two fire damage applications.
|
|
*
|
|
* @param fireDamagePeriod the delay
|
|
* @param timeUnit the time unit
|
|
*/
|
|
public void setFireDamagePeriod(long fireDamagePeriod, @NotNull TimeUnit timeUnit) {
|
|
fireDamagePeriod = timeUnit.toMilliseconds(fireDamagePeriod);
|
|
this.fireDamagePeriod = fireDamagePeriod;
|
|
}
|
|
|
|
/**
|
|
* Changes the {@link Team} for the entity.
|
|
*
|
|
* @param team The new team
|
|
*/
|
|
public void setTeam(Team team) {
|
|
if (this.team == team) return;
|
|
|
|
String member;
|
|
|
|
if (this instanceof Player) {
|
|
Player player = (Player) this;
|
|
member = player.getUsername();
|
|
} else {
|
|
member = this.uuid.toString();
|
|
}
|
|
|
|
if (this.team != null) {
|
|
this.team.removeMember(member);
|
|
}
|
|
|
|
this.team = team;
|
|
if (team != null) {
|
|
team.addMember(member);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the {@link Team} of the entity.
|
|
*
|
|
* @return the {@link Team}
|
|
*/
|
|
public Team getTeam() {
|
|
return team;
|
|
}
|
|
|
|
/**
|
|
* Gets the line of sight in {@link BlockPosition} of the entity.
|
|
*
|
|
* @param maxDistance The max distance to scan
|
|
* @return A list of {@link BlockPosition} in this entities line of sight
|
|
*/
|
|
public List<BlockPosition> getLineOfSight(int maxDistance) {
|
|
List<BlockPosition> blocks = new ArrayList<>();
|
|
Iterator<BlockPosition> it = new BlockIterator(this, maxDistance);
|
|
while (it.hasNext()) {
|
|
BlockPosition position = it.next();
|
|
if (Block.fromStateId(getInstance().getBlockStateId(position)) != Block.AIR) blocks.add(position);
|
|
}
|
|
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<BlockPosition> 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.
|
|
*
|
|
* @param maxDistance The max distance to scan before returning null
|
|
* @return The {@link BlockPosition} targeted by this entity, null if non are found
|
|
*/
|
|
public BlockPosition getTargetBlockPosition(int maxDistance) {
|
|
Iterator<BlockPosition> it = new BlockIterator(this, maxDistance);
|
|
while (it.hasNext()) {
|
|
BlockPosition position = it.next();
|
|
if (Block.fromStateId(getInstance().getBlockStateId(position)) != Block.AIR) return position;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets {@link net.minestom.server.entity.metadata.EntityMeta} of this entity casted to {@link LivingEntityMeta}.
|
|
*
|
|
* @return null if meta of this entity does not inherit {@link LivingEntityMeta}, casted value otherwise.
|
|
*/
|
|
public LivingEntityMeta getLivingEntityMeta() {
|
|
if (this.entityMeta instanceof LivingEntityMeta) {
|
|
return (LivingEntityMeta) this.entityMeta;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
}
|