package org.bukkit.craftbukkit.entity; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.util.List; import java.util.Set; import java.util.UUID; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.network.chat.IChatBaseComponent; import net.minecraft.server.level.EntityPlayer; import net.minecraft.server.level.PlayerChunkMap; import net.minecraft.server.level.WorldServer; import net.minecraft.server.network.ServerPlayerConnection; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.boss.EntityComplexPart; import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon; import net.minecraft.world.entity.player.EntityHuman; import net.minecraft.world.entity.projectile.EntityArrow; import net.minecraft.world.phys.AxisAlignedBB; import org.bukkit.EntityEffect; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.block.BlockFace; import org.bukkit.block.PistonMoveReaction; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftSound; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftSpawnCategory; import org.bukkit.craftbukkit.util.CraftVector; import org.bukkit.entity.EntitySnapshot; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.entity.Pose; import org.bukkit.entity.SpawnCategory; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.metadata.MetadataValue; import org.bukkit.permissions.PermissibleBase; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.ServerOperator; import org.bukkit.plugin.Plugin; import org.bukkit.util.BoundingBox; import org.bukkit.util.NumberConversions; import org.bukkit.util.Vector; public abstract class CraftEntity implements org.bukkit.entity.Entity { private static PermissibleBase perm; private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); protected final CraftServer server; protected Entity entity; private final EntityType entityType; private EntityDamageEvent lastDamageEvent; private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY); public CraftEntity(final CraftServer server, final Entity entity) { this.server = server; this.entity = entity; this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); } public static CraftEntity getEntity(CraftServer server, T entity) { Preconditions.checkArgument(entity != null, "Unknown entity"); // Special case human, since bukkit use Player interface for ... if (entity instanceof EntityHuman && !(entity instanceof EntityPlayer)) { return new CraftHumanEntity(server, (EntityHuman) entity); } // Special case complex part, since there is no extra entity type for them if (entity instanceof EntityComplexPart complexPart) { if (complexPart.parentMob instanceof EntityEnderDragon) { return new CraftEnderDragonPart(server, complexPart); } else { return new CraftComplexPart(server, complexPart); } } CraftEntityTypes.EntityTypeData entityTypeData = CraftEntityTypes.getEntityTypeData(CraftEntityType.minecraftToBukkit(entity.getType())); if (entityTypeData != null) { return entityTypeData.convertFunction().apply(server, entity); } throw new AssertionError("Unknown entity " + (entity == null ? null : entity.getClass())); } @Override public Location getLocation() { return CraftLocation.toBukkit(entity.position(), getWorld(), entity.getBukkitYaw(), entity.getXRot()); } @Override public Location getLocation(Location loc) { if (loc != null) { loc.setWorld(getWorld()); loc.setX(entity.getX()); loc.setY(entity.getY()); loc.setZ(entity.getZ()); loc.setYaw(entity.getBukkitYaw()); loc.setPitch(entity.getXRot()); } return loc; } @Override public Vector getVelocity() { return CraftVector.toBukkit(entity.getDeltaMovement()); } @Override public void setVelocity(Vector velocity) { Preconditions.checkArgument(velocity != null, "velocity"); velocity.checkFinite(); entity.setDeltaMovement(CraftVector.toNMS(velocity)); entity.hurtMarked = true; } @Override public double getHeight() { return getHandle().getBbHeight(); } @Override public double getWidth() { return getHandle().getBbWidth(); } @Override public BoundingBox getBoundingBox() { AxisAlignedBB bb = getHandle().getBoundingBox(); return new BoundingBox(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); } @Override public boolean isOnGround() { if (entity instanceof EntityArrow) { return ((EntityArrow) entity).inGround; } return entity.onGround(); } @Override public boolean isInWater() { return entity.isInWater(); } @Override public World getWorld() { return entity.level().getWorld(); } @Override public void setRotation(float yaw, float pitch) { NumberConversions.checkFinite(pitch, "pitch not finite"); NumberConversions.checkFinite(yaw, "yaw not finite"); yaw = Location.normalizeYaw(yaw); pitch = Location.normalizePitch(pitch); entity.setYRot(yaw); entity.setXRot(pitch); entity.yRotO = yaw; entity.xRotO = pitch; entity.setYHeadRot(yaw); } @Override public boolean teleport(Location location) { return teleport(location, TeleportCause.PLUGIN); } @Override public boolean teleport(Location location, TeleportCause cause) { Preconditions.checkArgument(location != null, "location cannot be null"); location.checkFinite(); if (entity.isVehicle() || entity.isRemoved()) { return false; } // If this entity is riding another entity, we must dismount before teleporting. entity.stopRiding(); // Let the server handle cross world teleports if (location.getWorld() != null && !location.getWorld().equals(getWorld())) { // Prevent teleportation to an other world during world generation Preconditions.checkState(!entity.generation, "Cannot teleport entity to an other world during world generation"); entity.teleportTo(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location)); return true; } // entity.setLocation() throws no event, and so cannot be cancelled entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); // SPIGOT-619: Force sync head rotation also entity.setYHeadRot(location.getYaw()); return true; } @Override public boolean teleport(org.bukkit.entity.Entity destination) { return teleport(destination.getLocation()); } @Override public boolean teleport(org.bukkit.entity.Entity destination, TeleportCause cause) { return teleport(destination.getLocation(), cause); } @Override public List getNearbyEntities(double x, double y, double z) { Preconditions.checkState(!entity.generation, "Cannot get nearby entities during world generation"); List notchEntityList = entity.level().getEntities(entity, entity.getBoundingBox().inflate(x, y, z), Predicates.alwaysTrue()); List bukkitEntityList = new java.util.ArrayList(notchEntityList.size()); for (Entity e : notchEntityList) { bukkitEntityList.add(e.getBukkitEntity()); } return bukkitEntityList; } @Override public int getEntityId() { return entity.getId(); } @Override public int getFireTicks() { return entity.getRemainingFireTicks(); } @Override public int getMaxFireTicks() { return entity.getFireImmuneTicks(); } @Override public void setFireTicks(int ticks) { entity.setRemainingFireTicks(ticks); } @Override public void setVisualFire(boolean fire) { getHandle().hasVisualFire = fire; } @Override public boolean isVisualFire() { return getHandle().hasVisualFire; } @Override public int getFreezeTicks() { return getHandle().getTicksFrozen(); } @Override public int getMaxFreezeTicks() { return getHandle().getTicksRequiredToFreeze(); } @Override public void setFreezeTicks(int ticks) { Preconditions.checkArgument(0 <= ticks, "Ticks (%s) cannot be less than 0", ticks); getHandle().setTicksFrozen(ticks); } @Override public boolean isFrozen() { return getHandle().isFullyFrozen(); } @Override public void remove() { entity.pluginRemoved = true; entity.discard(); } @Override public boolean isDead() { return !entity.isAlive(); } @Override public boolean isValid() { return entity.isAlive() && entity.valid && entity.isChunkLoaded() && isInWorld(); } @Override public Server getServer() { return server; } @Override public boolean isPersistent() { return entity.persist; } @Override public void setPersistent(boolean persistent) { entity.persist = persistent; } public Vector getMomentum() { return getVelocity(); } public void setMomentum(Vector value) { setVelocity(value); } @Override public org.bukkit.entity.Entity getPassenger() { return isEmpty() ? null : getHandle().passengers.get(0).getBukkitEntity(); } @Override public boolean setPassenger(org.bukkit.entity.Entity passenger) { Preconditions.checkArgument(!this.equals(passenger), "Entity cannot ride itself."); if (passenger instanceof CraftEntity) { eject(); return ((CraftEntity) passenger).getHandle().startRiding(getHandle()); } else { return false; } } @Override public List getPassengers() { return Lists.newArrayList(Lists.transform(getHandle().passengers, (Function) input -> input.getBukkitEntity())); } @Override public boolean addPassenger(org.bukkit.entity.Entity passenger) { Preconditions.checkArgument(passenger != null, "Entity passenger cannot be null"); Preconditions.checkArgument(!this.equals(passenger), "Entity cannot ride itself."); return ((CraftEntity) passenger).getHandle().startRiding(getHandle(), true); } @Override public boolean removePassenger(org.bukkit.entity.Entity passenger) { Preconditions.checkArgument(passenger != null, "Entity passenger cannot be null"); ((CraftEntity) passenger).getHandle().stopRiding(); return true; } @Override public boolean isEmpty() { return !getHandle().isVehicle(); } @Override public boolean eject() { if (isEmpty()) { return false; } getHandle().ejectPassengers(); return true; } @Override public float getFallDistance() { return getHandle().fallDistance; } @Override public void setFallDistance(float distance) { getHandle().fallDistance = distance; } @Override public void setLastDamageCause(EntityDamageEvent event) { lastDamageEvent = event; } @Override public EntityDamageEvent getLastDamageCause() { return lastDamageEvent; } @Override public UUID getUniqueId() { return getHandle().getUUID(); } @Override public int getTicksLived() { return getHandle().tickCount; } @Override public void setTicksLived(int value) { Preconditions.checkArgument(value > 0, "Age value (%s) must be greater than 0", value); getHandle().tickCount = value; } public Entity getHandle() { return entity; } @Override public final EntityType getType() { return entityType; } @Override public void playEffect(EntityEffect type) { Preconditions.checkArgument(type != null, "Type cannot be null"); Preconditions.checkState(!entity.generation, "Cannot play effect during world generation"); if (type.getApplicable().isInstance(this)) { this.getHandle().level().broadcastEntityEvent(getHandle(), type.getData()); } } @Override public Sound getSwimSound() { return CraftSound.minecraftToBukkit(getHandle().getSwimSound0()); } @Override public Sound getSwimSplashSound() { return CraftSound.minecraftToBukkit(getHandle().getSwimSplashSound0()); } @Override public Sound getSwimHighSpeedSplashSound() { return CraftSound.minecraftToBukkit(getHandle().getSwimHighSpeedSplashSound0()); } public void setHandle(final Entity entity) { this.entity = entity; } @Override public String toString() { return "CraftEntity{" + "id=" + getEntityId() + '}'; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final CraftEntity other = (CraftEntity) obj; return (this.getEntityId() == other.getEntityId()); } @Override public int hashCode() { int hash = 7; hash = 29 * hash + this.getEntityId(); return hash; } @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue); } @Override public List getMetadata(String metadataKey) { return server.getEntityMetadata().getMetadata(this, metadataKey); } @Override public boolean hasMetadata(String metadataKey) { return server.getEntityMetadata().hasMetadata(this, metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) { server.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin); } @Override public boolean isInsideVehicle() { return getHandle().isPassenger(); } @Override public boolean leaveVehicle() { if (!isInsideVehicle()) { return false; } getHandle().stopRiding(); return true; } @Override public org.bukkit.entity.Entity getVehicle() { if (!isInsideVehicle()) { return null; } return getHandle().getVehicle().getBukkitEntity(); } @Override public void setCustomName(String name) { // sane limit for name length if (name != null && name.length() > 256) { name = name.substring(0, 256); } getHandle().setCustomName(CraftChatMessage.fromStringOrNull(name)); } @Override public String getCustomName() { IChatBaseComponent name = getHandle().getCustomName(); if (name == null) { return null; } return CraftChatMessage.fromComponent(name); } @Override public void setCustomNameVisible(boolean flag) { getHandle().setCustomNameVisible(flag); } @Override public boolean isCustomNameVisible() { return getHandle().isCustomNameVisible(); } @Override public void setVisibleByDefault(boolean visible) { if (getHandle().visibleByDefault != visible) { if (visible) { // Making visible by default, reset and show to all players for (Player player : server.getOnlinePlayers()) { ((CraftPlayer) player).resetAndShowEntity(this); } } else { // Hiding by default, reset and hide from all players for (Player player : server.getOnlinePlayers()) { ((CraftPlayer) player).resetAndHideEntity(this); } } getHandle().visibleByDefault = visible; } } @Override public boolean isVisibleByDefault() { return getHandle().visibleByDefault; } @Override public Set getTrackedBy() { Preconditions.checkState(!entity.generation, "Cannot get tracking players during world generation"); ImmutableSet.Builder players = ImmutableSet.builder(); WorldServer world = ((CraftWorld) getWorld()).getHandle(); PlayerChunkMap.EntityTracker entityTracker = world.getChunkSource().chunkMap.entityMap.get(getEntityId()); if (entityTracker != null) { for (ServerPlayerConnection connection : entityTracker.seenBy) { players.add(connection.getPlayer().getBukkitEntity()); } } return players.build(); } @Override public void sendMessage(String message) { } @Override public void sendMessage(String... messages) { } @Override public void sendMessage(UUID sender, String message) { this.sendMessage(message); // Most entities don't know about senders } @Override public void sendMessage(UUID sender, String... messages) { this.sendMessage(messages); // Most entities don't know about senders } @Override public String getName() { return CraftChatMessage.fromComponent(getHandle().getName()); } @Override public boolean isPermissionSet(String name) { return getPermissibleBase().isPermissionSet(name); } @Override public boolean isPermissionSet(Permission perm) { return CraftEntity.getPermissibleBase().isPermissionSet(perm); } @Override public boolean hasPermission(String name) { return getPermissibleBase().hasPermission(name); } @Override public boolean hasPermission(Permission perm) { return getPermissibleBase().hasPermission(perm); } @Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { return getPermissibleBase().addAttachment(plugin, name, value); } @Override public PermissionAttachment addAttachment(Plugin plugin) { return getPermissibleBase().addAttachment(plugin); } @Override public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { return getPermissibleBase().addAttachment(plugin, name, value, ticks); } @Override public PermissionAttachment addAttachment(Plugin plugin, int ticks) { return getPermissibleBase().addAttachment(plugin, ticks); } @Override public void removeAttachment(PermissionAttachment attachment) { getPermissibleBase().removeAttachment(attachment); } @Override public void recalculatePermissions() { getPermissibleBase().recalculatePermissions(); } @Override public Set getEffectivePermissions() { return getPermissibleBase().getEffectivePermissions(); } @Override public boolean isOp() { return getPermissibleBase().isOp(); } @Override public void setOp(boolean value) { getPermissibleBase().setOp(value); } @Override public void setGlowing(boolean flag) { getHandle().setGlowingTag(flag); } @Override public boolean isGlowing() { return getHandle().isCurrentlyGlowing(); } @Override public void setInvulnerable(boolean flag) { getHandle().setInvulnerable(flag); } @Override public boolean isInvulnerable() { return getHandle().isInvulnerableTo(getHandle().damageSources().generic()); } @Override public boolean isSilent() { return getHandle().isSilent(); } @Override public void setSilent(boolean flag) { getHandle().setSilent(flag); } @Override public boolean hasGravity() { return !getHandle().isNoGravity(); } @Override public void setGravity(boolean gravity) { getHandle().setNoGravity(!gravity); } @Override public int getPortalCooldown() { return getHandle().portalCooldown; } @Override public void setPortalCooldown(int cooldown) { getHandle().portalCooldown = cooldown; } @Override public Set getScoreboardTags() { return getHandle().getTags(); } @Override public boolean addScoreboardTag(String tag) { return getHandle().addTag(tag); } @Override public boolean removeScoreboardTag(String tag) { return getHandle().removeTag(tag); } @Override public PistonMoveReaction getPistonMoveReaction() { return PistonMoveReaction.getById(getHandle().getPistonPushReaction().ordinal()); } @Override public BlockFace getFacing() { // Use this method over getDirection because it handles boats and minecarts. return CraftBlock.notchToBlockFace(getHandle().getMotionDirection()); } @Override public CraftPersistentDataContainer getPersistentDataContainer() { return persistentDataContainer; } @Override public Pose getPose() { return Pose.values()[getHandle().getPose().ordinal()]; } @Override public SpawnCategory getSpawnCategory() { return CraftSpawnCategory.toBukkit(getHandle().getType().getCategory()); } @Override public boolean isInWorld() { return getHandle().inWorld; } @Override public EntitySnapshot createSnapshot() { return CraftEntitySnapshot.create(this); } @Override public org.bukkit.entity.Entity copy() { Entity copy = copy(getHandle().level()); Preconditions.checkArgument(copy != null, "Error creating new entity."); return copy.getBukkitEntity(); } @Override public org.bukkit.entity.Entity copy(Location location) { Preconditions.checkArgument(location.getWorld() != null, "Location has no world"); Entity copy = copy(((CraftWorld) location.getWorld()).getHandle()); Preconditions.checkArgument(copy != null, "Error creating new entity."); copy.setPos(location.getX(), location.getY(), location.getZ()); return location.getWorld().addEntity(copy.getBukkitEntity()); } private Entity copy(net.minecraft.world.level.World level) { NBTTagCompound compoundTag = new NBTTagCompound(); getHandle().saveAsPassenger(compoundTag, false); return EntityTypes.loadEntityRecursive(compoundTag, level, java.util.function.Function.identity()); } public void storeBukkitValues(NBTTagCompound c) { if (!this.persistentDataContainer.isEmpty()) { c.put("BukkitValues", this.persistentDataContainer.toTagCompound()); } } public void readBukkitValues(NBTTagCompound c) { NBTBase base = c.get("BukkitValues"); if (base instanceof NBTTagCompound) { this.persistentDataContainer.putAll((NBTTagCompound) base); } } protected NBTTagCompound save() { NBTTagCompound nbttagcompound = new NBTTagCompound(); nbttagcompound.putString("id", getHandle().getEncodeId()); getHandle().saveWithoutId(nbttagcompound); return nbttagcompound; } // re-sends the spawn entity packet to updated values which cannot be updated otherwise protected void update() { if (!getHandle().isAlive()) { return; } WorldServer world = ((CraftWorld) getWorld()).getHandle(); PlayerChunkMap.EntityTracker entityTracker = world.getChunkSource().chunkMap.entityMap.get(getEntityId()); if (entityTracker == null) { return; } entityTracker.broadcast(getHandle().getAddEntityPacket()); } private static PermissibleBase getPermissibleBase() { if (perm == null) { perm = new PermissibleBase(new ServerOperator() { @Override public boolean isOp() { return false; } @Override public void setOp(boolean value) { } }); } return perm; } }