CraftBukkit/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
2023-12-06 03:40:00 +11:00

800 lines
29 KiB
Java

package org.bukkit.craftbukkit.entity;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket;
import net.minecraft.server.level.WorldServer;
import net.minecraft.sounds.SoundEffect;
import net.minecraft.world.EnumHand;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectList;
import net.minecraft.world.entity.EntityInsentient;
import net.minecraft.world.entity.EntityLiving;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.entity.EnumMonsterType;
import net.minecraft.world.entity.ai.attributes.GenericAttributes;
import net.minecraft.world.entity.boss.wither.EntityWither;
import net.minecraft.world.entity.decoration.EntityArmorStand;
import net.minecraft.world.entity.player.EntityHuman;
import net.minecraft.world.entity.projectile.EntityArrow;
import net.minecraft.world.entity.projectile.EntityDragonFireball;
import net.minecraft.world.entity.projectile.EntityEgg;
import net.minecraft.world.entity.projectile.EntityEnderPearl;
import net.minecraft.world.entity.projectile.EntityFireball;
import net.minecraft.world.entity.projectile.EntityFireworks;
import net.minecraft.world.entity.projectile.EntityFishingHook;
import net.minecraft.world.entity.projectile.EntityLargeFireball;
import net.minecraft.world.entity.projectile.EntityLlamaSpit;
import net.minecraft.world.entity.projectile.EntityPotion;
import net.minecraft.world.entity.projectile.EntityProjectile;
import net.minecraft.world.entity.projectile.EntityShulkerBullet;
import net.minecraft.world.entity.projectile.EntitySmallFireball;
import net.minecraft.world.entity.projectile.EntitySnowball;
import net.minecraft.world.entity.projectile.EntitySpectralArrow;
import net.minecraft.world.entity.projectile.EntityThrownExpBottle;
import net.minecraft.world.entity.projectile.EntityThrownTrident;
import net.minecraft.world.entity.projectile.EntityTippedArrow;
import net.minecraft.world.entity.projectile.EntityWitherSkull;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftSound;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.memory.CraftMemoryKey;
import org.bukkit.craftbukkit.entity.memory.CraftMemoryMapper;
import org.bukkit.craftbukkit.inventory.CraftEntityEquipment;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
import org.bukkit.entity.AbstractArrow;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.DragonFireball;
import org.bukkit.entity.Egg;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityCategory;
import org.bukkit.entity.Fireball;
import org.bukkit.entity.Firework;
import org.bukkit.entity.FishHook;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.LingeringPotion;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.LlamaSpit;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.ShulkerBullet;
import org.bukkit.entity.SmallFireball;
import org.bukkit.entity.Snowball;
import org.bukkit.entity.SpectralArrow;
import org.bukkit.entity.ThrownExpBottle;
import org.bukkit.entity.ThrownPotion;
import org.bukkit.entity.TippedArrow;
import org.bukkit.entity.Trident;
import org.bukkit.entity.WindCharge;
import org.bukkit.entity.WitherSkull;
import org.bukkit.entity.memory.MemoryKey;
import org.bukkit.event.entity.EntityPotionEffectEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.bukkit.util.BlockIterator;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
public class CraftLivingEntity extends CraftEntity implements LivingEntity {
private CraftEntityEquipment equipment;
public CraftLivingEntity(final CraftServer server, final EntityLiving entity) {
super(server, entity);
if (entity instanceof EntityInsentient || entity instanceof EntityArmorStand) {
equipment = new CraftEntityEquipment(this);
}
}
@Override
public double getHealth() {
return Math.min(Math.max(0, getHandle().getHealth()), getMaxHealth());
}
@Override
public void setHealth(double health) {
health = (float) health;
Preconditions.checkArgument(health >= 0 && health <= this.getMaxHealth(), "Health value (%s) must be between 0 and %s", health, this.getMaxHealth());
// during world generation, we don't want to run logic for dropping items and xp
if (getHandle().generation && health == 0) {
getHandle().discard();
return;
}
getHandle().setHealth((float) health);
if (health == 0) {
getHandle().die(getHandle().damageSources().generic());
}
}
@Override
public double getAbsorptionAmount() {
return getHandle().getAbsorptionAmount();
}
@Override
public void setAbsorptionAmount(double amount) {
Preconditions.checkArgument(amount >= 0 && Double.isFinite(amount), "amount < 0 or non-finite");
getHandle().setAbsorptionAmount((float) amount);
}
@Override
public double getMaxHealth() {
return getHandle().getMaxHealth();
}
@Override
public void setMaxHealth(double amount) {
Preconditions.checkArgument(amount > 0, "Max health amount (%s) must be greater than 0", amount);
getHandle().getAttribute(GenericAttributes.MAX_HEALTH).setBaseValue(amount);
if (getHealth() > amount) {
setHealth(amount);
}
}
@Override
public void resetMaxHealth() {
setMaxHealth(getHandle().getAttribute(GenericAttributes.MAX_HEALTH).getAttribute().getDefaultValue());
}
@Override
public double getEyeHeight() {
return getHandle().getEyeHeight();
}
@Override
public double getEyeHeight(boolean ignorePose) {
return getEyeHeight();
}
private List<Block> getLineOfSight(Set<Material> transparent, int maxDistance, int maxLength) {
Preconditions.checkState(!getHandle().generation, "Cannot get line of sight during world generation");
if (transparent == null) {
transparent = Sets.newHashSet(Material.AIR, Material.CAVE_AIR, Material.VOID_AIR);
}
if (maxDistance > 120) {
maxDistance = 120;
}
ArrayList<Block> blocks = new ArrayList<Block>();
Iterator<Block> itr = new BlockIterator(this, maxDistance);
while (itr.hasNext()) {
Block block = itr.next();
blocks.add(block);
if (maxLength != 0 && blocks.size() > maxLength) {
blocks.remove(0);
}
Material material = block.getType();
if (!transparent.contains(material)) {
break;
}
}
return blocks;
}
@Override
public List<Block> getLineOfSight(Set<Material> transparent, int maxDistance) {
return getLineOfSight(transparent, maxDistance, 0);
}
@Override
public Block getTargetBlock(Set<Material> transparent, int maxDistance) {
List<Block> blocks = getLineOfSight(transparent, maxDistance, 1);
return blocks.get(0);
}
@Override
public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance) {
return getLineOfSight(transparent, maxDistance, 2);
}
@Override
public Block getTargetBlockExact(int maxDistance) {
return this.getTargetBlockExact(maxDistance, FluidCollisionMode.NEVER);
}
@Override
public Block getTargetBlockExact(int maxDistance, FluidCollisionMode fluidCollisionMode) {
RayTraceResult hitResult = this.rayTraceBlocks(maxDistance, fluidCollisionMode);
return (hitResult != null ? hitResult.getHitBlock() : null);
}
@Override
public RayTraceResult rayTraceBlocks(double maxDistance) {
return this.rayTraceBlocks(maxDistance, FluidCollisionMode.NEVER);
}
@Override
public RayTraceResult rayTraceBlocks(double maxDistance, FluidCollisionMode fluidCollisionMode) {
Preconditions.checkState(!getHandle().generation, "Cannot ray tray blocks during world generation");
Location eyeLocation = this.getEyeLocation();
Vector direction = eyeLocation.getDirection();
return this.getWorld().rayTraceBlocks(eyeLocation, direction, maxDistance, fluidCollisionMode, false);
}
@Override
public int getRemainingAir() {
return getHandle().getAirSupply();
}
@Override
public void setRemainingAir(int ticks) {
getHandle().setAirSupply(ticks);
}
@Override
public int getMaximumAir() {
return getHandle().maxAirTicks;
}
@Override
public void setMaximumAir(int ticks) {
getHandle().maxAirTicks = ticks;
}
@Override
public int getArrowCooldown() {
return getHandle().removeArrowTime;
}
@Override
public void setArrowCooldown(int ticks) {
getHandle().removeArrowTime = ticks;
}
@Override
public int getArrowsInBody() {
return getHandle().getArrowCount();
}
@Override
public void setArrowsInBody(int count) {
Preconditions.checkArgument(count >= 0, "New arrow amount must be >= 0");
getHandle().getEntityData().set(EntityLiving.DATA_ARROW_COUNT_ID, count);
}
@Override
public void damage(double amount) {
damage(amount, null);
}
@Override
public void damage(double amount, org.bukkit.entity.Entity source) {
Preconditions.checkState(!getHandle().generation, "Cannot damage entity during world generation");
DamageSource reason = getHandle().damageSources().generic();
if (source instanceof HumanEntity) {
reason = getHandle().damageSources().playerAttack(((CraftHumanEntity) source).getHandle());
} else if (source instanceof LivingEntity) {
reason = getHandle().damageSources().mobAttack(((CraftLivingEntity) source).getHandle());
}
entity.hurt(reason, (float) amount);
}
@Override
public Location getEyeLocation() {
Location loc = getLocation();
loc.setY(loc.getY() + getEyeHeight());
return loc;
}
@Override
public int getMaximumNoDamageTicks() {
return getHandle().invulnerableDuration;
}
@Override
public void setMaximumNoDamageTicks(int ticks) {
getHandle().invulnerableDuration = ticks;
}
@Override
public double getLastDamage() {
return getHandle().lastHurt;
}
@Override
public void setLastDamage(double damage) {
getHandle().lastHurt = (float) damage;
}
@Override
public int getNoDamageTicks() {
return getHandle().invulnerableTime;
}
@Override
public void setNoDamageTicks(int ticks) {
getHandle().invulnerableTime = ticks;
}
@Override
public int getNoActionTicks() {
return getHandle().getNoActionTime();
}
@Override
public void setNoActionTicks(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks must be >= 0");
getHandle().setNoActionTime(ticks);
}
@Override
public EntityLiving getHandle() {
return (EntityLiving) entity;
}
public void setHandle(final EntityLiving entity) {
super.setHandle(entity);
}
@Override
public String toString() {
return "CraftLivingEntity{" + "id=" + getEntityId() + '}';
}
@Override
public Player getKiller() {
return getHandle().lastHurtByPlayer == null ? null : (Player) getHandle().lastHurtByPlayer.getBukkitEntity();
}
@Override
public boolean addPotionEffect(PotionEffect effect) {
return addPotionEffect(effect, false);
}
@Override
public boolean addPotionEffect(PotionEffect effect, boolean force) {
getHandle().addEffect(new MobEffect(CraftPotionEffectType.bukkitToMinecraft(effect.getType()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()), EntityPotionEffectEvent.Cause.PLUGIN);
return true;
}
@Override
public boolean addPotionEffects(Collection<PotionEffect> effects) {
boolean success = true;
for (PotionEffect effect : effects) {
success &= addPotionEffect(effect);
}
return success;
}
@Override
public boolean hasPotionEffect(PotionEffectType type) {
return getHandle().hasEffect(CraftPotionEffectType.bukkitToMinecraft(type));
}
@Override
public PotionEffect getPotionEffect(PotionEffectType type) {
MobEffect handle = getHandle().getEffect(CraftPotionEffectType.bukkitToMinecraft(type));
return (handle == null) ? null : new PotionEffect(CraftPotionEffectType.minecraftToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible());
}
@Override
public void removePotionEffect(PotionEffectType type) {
getHandle().removeEffect(CraftPotionEffectType.bukkitToMinecraft(type), EntityPotionEffectEvent.Cause.PLUGIN);
}
@Override
public Collection<PotionEffect> getActivePotionEffects() {
List<PotionEffect> effects = new ArrayList<PotionEffect>();
for (MobEffect handle : getHandle().activeEffects.values()) {
effects.add(new PotionEffect(CraftPotionEffectType.minecraftToBukkit(handle.getEffect()), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()));
}
return effects;
}
@Override
public <T extends Projectile> T launchProjectile(Class<? extends T> projectile) {
return launchProjectile(projectile, null);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Projectile> T launchProjectile(Class<? extends T> projectile, Vector velocity) {
Preconditions.checkState(!getHandle().generation, "Cannot launch projectile during world generation");
net.minecraft.world.level.World world = ((CraftWorld) getWorld()).getHandle();
net.minecraft.world.entity.Entity launch = null;
if (Snowball.class.isAssignableFrom(projectile)) {
launch = new EntitySnowball(world, getHandle());
((EntityProjectile) launch).shootFromRotation(getHandle(), getHandle().getXRot(), getHandle().getYRot(), 0.0F, 1.5F, 1.0F); // ItemSnowball
} else if (Egg.class.isAssignableFrom(projectile)) {
launch = new EntityEgg(world, getHandle());
((EntityProjectile) launch).shootFromRotation(getHandle(), getHandle().getXRot(), getHandle().getYRot(), 0.0F, 1.5F, 1.0F); // ItemEgg
} else if (EnderPearl.class.isAssignableFrom(projectile)) {
launch = new EntityEnderPearl(world, getHandle());
((EntityProjectile) launch).shootFromRotation(getHandle(), getHandle().getXRot(), getHandle().getYRot(), 0.0F, 1.5F, 1.0F); // ItemEnderPearl
} else if (AbstractArrow.class.isAssignableFrom(projectile)) {
if (TippedArrow.class.isAssignableFrom(projectile)) {
launch = new EntityTippedArrow(world, getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.ARROW));
((Arrow) launch.getBukkitEntity()).setBasePotionData(new PotionData(PotionType.WATER, false, false));
} else if (SpectralArrow.class.isAssignableFrom(projectile)) {
launch = new EntitySpectralArrow(world, getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.SPECTRAL_ARROW));
} else if (Trident.class.isAssignableFrom(projectile)) {
launch = new EntityThrownTrident(world, getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TRIDENT));
} else {
launch = new EntityTippedArrow(world, getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.ARROW));
}
((EntityArrow) launch).shootFromRotation(getHandle(), getHandle().getXRot(), getHandle().getYRot(), 0.0F, 3.0F, 1.0F); // ItemBow
} else if (ThrownPotion.class.isAssignableFrom(projectile)) {
if (LingeringPotion.class.isAssignableFrom(projectile)) {
launch = new EntityPotion(world, getHandle());
((EntityPotion) launch).setItem(CraftItemStack.asNMSCopy(new ItemStack(org.bukkit.Material.LINGERING_POTION, 1)));
} else {
launch = new EntityPotion(world, getHandle());
((EntityPotion) launch).setItem(CraftItemStack.asNMSCopy(new ItemStack(org.bukkit.Material.SPLASH_POTION, 1)));
}
((EntityProjectile) launch).shootFromRotation(getHandle(), getHandle().getXRot(), getHandle().getYRot(), -20.0F, 0.5F, 1.0F); // ItemSplashPotion
} else if (ThrownExpBottle.class.isAssignableFrom(projectile)) {
launch = new EntityThrownExpBottle(world, getHandle());
((EntityProjectile) launch).shootFromRotation(getHandle(), getHandle().getXRot(), getHandle().getYRot(), -20.0F, 0.7F, 1.0F); // ItemExpBottle
} else if (FishHook.class.isAssignableFrom(projectile) && getHandle() instanceof EntityHuman) {
launch = new EntityFishingHook((EntityHuman) getHandle(), world, 0, 0);
} else if (Fireball.class.isAssignableFrom(projectile)) {
Location location = getEyeLocation();
Vector direction = location.getDirection().multiply(10);
if (SmallFireball.class.isAssignableFrom(projectile)) {
launch = new EntitySmallFireball(world, getHandle(), direction.getX(), direction.getY(), direction.getZ());
} else if (WitherSkull.class.isAssignableFrom(projectile)) {
launch = new EntityWitherSkull(world, getHandle(), direction.getX(), direction.getY(), direction.getZ());
} else if (DragonFireball.class.isAssignableFrom(projectile)) {
launch = new EntityDragonFireball(world, getHandle(), direction.getX(), direction.getY(), direction.getZ());
} else if (WindCharge.class.isAssignableFrom(projectile)) {
launch = EntityTypes.WIND_CHARGE.create(world);
((net.minecraft.world.entity.projectile.WindCharge) launch).setOwner(getHandle());
((net.minecraft.world.entity.projectile.WindCharge) launch).setDirection(direction.getX(), direction.getY(), direction.getZ());
} else {
launch = new EntityLargeFireball(world, getHandle(), direction.getX(), direction.getY(), direction.getZ(), 1);
}
((EntityFireball) launch).projectileSource = this;
launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
} else if (LlamaSpit.class.isAssignableFrom(projectile)) {
Location location = getEyeLocation();
Vector direction = location.getDirection();
launch = EntityTypes.LLAMA_SPIT.create(world);
((EntityLlamaSpit) launch).setOwner(getHandle());
((EntityLlamaSpit) launch).shoot(direction.getX(), direction.getY(), direction.getZ(), 1.5F, 10.0F); // EntityLlama
launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
} else if (ShulkerBullet.class.isAssignableFrom(projectile)) {
Location location = getEyeLocation();
launch = new EntityShulkerBullet(world, getHandle(), null, null);
launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
} else if (Firework.class.isAssignableFrom(projectile)) {
Location location = getEyeLocation();
launch = new EntityFireworks(world, net.minecraft.world.item.ItemStack.EMPTY, getHandle());
launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
}
Preconditions.checkArgument(launch != null, "Projectile (%s) not supported", projectile.getName());
if (velocity != null) {
((T) launch.getBukkitEntity()).setVelocity(velocity);
}
world.addFreshEntity(launch);
return (T) launch.getBukkitEntity();
}
@Override
public boolean hasLineOfSight(Entity other) {
Preconditions.checkState(!getHandle().generation, "Cannot check line of sight during world generation");
return getHandle().hasLineOfSight(((CraftEntity) other).getHandle());
}
@Override
public boolean getRemoveWhenFarAway() {
return getHandle() instanceof EntityInsentient && !((EntityInsentient) getHandle()).isPersistenceRequired();
}
@Override
public void setRemoveWhenFarAway(boolean remove) {
if (getHandle() instanceof EntityInsentient) {
((EntityInsentient) getHandle()).setPersistenceRequired(!remove);
}
}
@Override
public EntityEquipment getEquipment() {
return equipment;
}
@Override
public void setCanPickupItems(boolean pickup) {
if (getHandle() instanceof EntityInsentient) {
((EntityInsentient) getHandle()).setCanPickUpLoot(pickup);
} else {
getHandle().bukkitPickUpLoot = pickup;
}
}
@Override
public boolean getCanPickupItems() {
if (getHandle() instanceof EntityInsentient) {
return ((EntityInsentient) getHandle()).canPickUpLoot();
} else {
return getHandle().bukkitPickUpLoot;
}
}
@Override
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) {
if (getHealth() == 0) {
return false;
}
return super.teleport(location, cause);
}
@Override
public boolean isLeashed() {
if (!(getHandle() instanceof EntityInsentient)) {
return false;
}
return ((EntityInsentient) getHandle()).getLeashHolder() != null;
}
@Override
public Entity getLeashHolder() throws IllegalStateException {
Preconditions.checkState(isLeashed(), "Entity not leashed");
return ((EntityInsentient) getHandle()).getLeashHolder().getBukkitEntity();
}
private boolean unleash() {
if (!isLeashed()) {
return false;
}
((EntityInsentient) getHandle()).dropLeash(true, false);
return true;
}
@Override
public boolean setLeashHolder(Entity holder) {
if (getHandle().generation || (getHandle() instanceof EntityWither) || !(getHandle() instanceof EntityInsentient)) {
return false;
}
if (holder == null) {
return unleash();
}
if (holder.isDead()) {
return false;
}
unleash();
((EntityInsentient) getHandle()).setLeashedTo(((CraftEntity) holder).getHandle(), true);
return true;
}
@Override
public boolean isGliding() {
return getHandle().getSharedFlag(7);
}
@Override
public void setGliding(boolean gliding) {
getHandle().setSharedFlag(7, gliding);
}
@Override
public boolean isSwimming() {
return getHandle().isSwimming();
}
@Override
public void setSwimming(boolean swimming) {
getHandle().setSwimming(swimming);
}
@Override
public boolean isRiptiding() {
return getHandle().isAutoSpinAttack();
}
@Override
public boolean isSleeping() {
return getHandle().isSleeping();
}
@Override
public boolean isClimbing() {
Preconditions.checkState(!getHandle().generation, "Cannot check if climbing during world generation");
return getHandle().onClimbable();
}
@Override
public AttributeInstance getAttribute(Attribute attribute) {
return getHandle().craftAttributes.getAttribute(attribute);
}
@Override
public void setAI(boolean ai) {
if (this.getHandle() instanceof EntityInsentient) {
((EntityInsentient) this.getHandle()).setNoAi(!ai);
}
}
@Override
public boolean hasAI() {
return (this.getHandle() instanceof EntityInsentient) ? !((EntityInsentient) this.getHandle()).isNoAi() : false;
}
@Override
public void attack(Entity target) {
Preconditions.checkArgument(target != null, "target == null");
Preconditions.checkState(!getHandle().generation, "Cannot attack during world generation");
if (getHandle() instanceof EntityHuman) {
((EntityHuman) getHandle()).attack(((CraftEntity) target).getHandle());
} else {
getHandle().doHurtTarget(((CraftEntity) target).getHandle());
}
}
@Override
public void swingMainHand() {
Preconditions.checkState(!getHandle().generation, "Cannot swing hand during world generation");
getHandle().swing(EnumHand.MAIN_HAND, true);
}
@Override
public void swingOffHand() {
Preconditions.checkState(!getHandle().generation, "Cannot swing hand during world generation");
getHandle().swing(EnumHand.OFF_HAND, true);
}
@Override
public void playHurtAnimation(float yaw) {
if (getHandle().level() instanceof WorldServer world) {
/*
* Vanilla degrees state that 0 = left, 90 = front, 180 = right, and 270 = behind.
* This makes no sense. We'll add 90 to it so that 0 = front, clockwise from there.
*/
float actualYaw = yaw + 90;
ClientboundHurtAnimationPacket packet = new ClientboundHurtAnimationPacket(getEntityId(), actualYaw);
world.getChunkSource().broadcastAndSend(getHandle(), packet);
}
}
@Override
public void setCollidable(boolean collidable) {
getHandle().collides = collidable;
}
@Override
public boolean isCollidable() {
return getHandle().collides;
}
@Override
public Set<UUID> getCollidableExemptions() {
return getHandle().collidableExemptions;
}
@Override
public <T> T getMemory(MemoryKey<T> memoryKey) {
return (T) getHandle().getBrain().getMemory(CraftMemoryKey.bukkitToMinecraft(memoryKey)).map(CraftMemoryMapper::fromNms).orElse(null);
}
@Override
public <T> void setMemory(MemoryKey<T> memoryKey, T t) {
getHandle().getBrain().setMemory(CraftMemoryKey.bukkitToMinecraft(memoryKey), CraftMemoryMapper.toNms(t));
}
@Override
public Sound getHurtSound() {
SoundEffect sound = getHandle().getHurtSound0(getHandle().damageSources().generic());
return (sound != null) ? CraftSound.minecraftToBukkit(sound) : null;
}
@Override
public Sound getDeathSound() {
SoundEffect sound = getHandle().getDeathSound0();
return (sound != null) ? CraftSound.minecraftToBukkit(sound) : null;
}
@Override
public Sound getFallDamageSound(int fallHeight) {
return CraftSound.minecraftToBukkit(getHandle().getFallDamageSound0(fallHeight));
}
@Override
public Sound getFallDamageSoundSmall() {
return CraftSound.minecraftToBukkit(getHandle().getFallSounds().small());
}
@Override
public Sound getFallDamageSoundBig() {
return CraftSound.minecraftToBukkit(getHandle().getFallSounds().big());
}
@Override
public Sound getDrinkingSound(ItemStack itemStack) {
Preconditions.checkArgument(itemStack != null, "itemStack must not be null");
return CraftSound.minecraftToBukkit(getHandle().getDrinkingSound0(CraftItemStack.asNMSCopy(itemStack)));
}
@Override
public Sound getEatingSound(ItemStack itemStack) {
Preconditions.checkArgument(itemStack != null, "itemStack must not be null");
return CraftSound.minecraftToBukkit(getHandle().getEatingSound0(CraftItemStack.asNMSCopy(itemStack)));
}
@Override
public boolean canBreatheUnderwater() {
return getHandle().canBreatheUnderwater();
}
@Override
public EntityCategory getCategory() {
EnumMonsterType type = getHandle().getMobType(); // Not actually an enum?
if (type == EnumMonsterType.UNDEFINED) {
return EntityCategory.NONE;
} else if (type == EnumMonsterType.UNDEAD) {
return EntityCategory.UNDEAD;
} else if (type == EnumMonsterType.ARTHROPOD) {
return EntityCategory.ARTHROPOD;
} else if (type == EnumMonsterType.ILLAGER) {
return EntityCategory.ILLAGER;
} else if (type == EnumMonsterType.WATER) {
return EntityCategory.WATER;
}
throw new UnsupportedOperationException("Unsupported monster type: " + type + ". This is a bug, report this to Spigot.");
}
@Override
public boolean isInvisible() {
return getHandle().isInvisible();
}
@Override
public void setInvisible(boolean invisible) {
getHandle().persistentInvisibility = invisible;
getHandle().setSharedFlag(5, invisible);
}
}