package net.citizensnpcs.nms.v1_13_R2.entity; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.Socket; import java.util.List; import java.util.Map; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.craftbukkit.v1_13_R2.CraftServer; import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.Plugin; import org.bukkit.util.Vector; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.mojang.authlib.GameProfile; import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.event.NPCEnderTeleportEvent; import net.citizensnpcs.api.event.NPCPushEvent; import net.citizensnpcs.api.npc.MetadataStore; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.trait.trait.Inventory; import net.citizensnpcs.nms.v1_13_R2.network.EmptyNetHandler; import net.citizensnpcs.nms.v1_13_R2.network.EmptyNetworkManager; import net.citizensnpcs.nms.v1_13_R2.network.EmptySocket; import net.citizensnpcs.nms.v1_13_R2.util.EmptyAdvancementDataPlayer; import net.citizensnpcs.nms.v1_13_R2.util.NMSImpl; import net.citizensnpcs.nms.v1_13_R2.util.PlayerControllerJump; import net.citizensnpcs.nms.v1_13_R2.util.PlayerControllerLook; import net.citizensnpcs.nms.v1_13_R2.util.PlayerControllerMove; import net.citizensnpcs.nms.v1_13_R2.util.PlayerNavigation; import net.citizensnpcs.npc.CitizensNPC; import net.citizensnpcs.npc.ai.NPCHolder; import net.citizensnpcs.npc.skin.SkinPacketTracker; import net.citizensnpcs.npc.skin.SkinnableEntity; import net.citizensnpcs.trait.Gravity; import net.citizensnpcs.util.NMS; import net.citizensnpcs.util.Util; import net.minecraft.server.v1_13_R2.AttributeInstance; import net.minecraft.server.v1_13_R2.BlockPosition; import net.minecraft.server.v1_13_R2.ChatComponentText; import net.minecraft.server.v1_13_R2.DamageSource; import net.minecraft.server.v1_13_R2.Entity; import net.minecraft.server.v1_13_R2.EntityHuman; import net.minecraft.server.v1_13_R2.EntityPlayer; import net.minecraft.server.v1_13_R2.EnumGamemode; import net.minecraft.server.v1_13_R2.EnumItemSlot; import net.minecraft.server.v1_13_R2.EnumProtocolDirection; import net.minecraft.server.v1_13_R2.GenericAttributes; import net.minecraft.server.v1_13_R2.IBlockData; import net.minecraft.server.v1_13_R2.IChatBaseComponent; import net.minecraft.server.v1_13_R2.MathHelper; import net.minecraft.server.v1_13_R2.MinecraftServer; import net.minecraft.server.v1_13_R2.NavigationAbstract; import net.minecraft.server.v1_13_R2.NetworkManager; import net.minecraft.server.v1_13_R2.Packet; import net.minecraft.server.v1_13_R2.PacketPlayOutEntityEquipment; import net.minecraft.server.v1_13_R2.PacketPlayOutEntityHeadRotation; import net.minecraft.server.v1_13_R2.PathType; import net.minecraft.server.v1_13_R2.PlayerInteractManager; import net.minecraft.server.v1_13_R2.WorldServer; public class EntityHumanNPC extends EntityPlayer implements NPCHolder, SkinnableEntity { private final Map bz = Maps.newEnumMap(PathType.class); private PlayerControllerJump controllerJump; private PlayerControllerLook controllerLook; private PlayerControllerMove controllerMove; private boolean isTracked = false; private int jumpTicks = 0; private PlayerNavigation navigation; private final CitizensNPC npc; private final Location packetLocationCache = new Location(null, 0, 0, 0); private final SkinPacketTracker skinTracker; private int updateCounter = 0; public EntityHumanNPC(MinecraftServer minecraftServer, WorldServer world, GameProfile gameProfile, PlayerInteractManager playerInteractManager, NPC npc) { super(minecraftServer, world, gameProfile, playerInteractManager); this.npc = (CitizensNPC) npc; if (npc != null) { skinTracker = new SkinPacketTracker(this); playerInteractManager.setGameMode(EnumGamemode.SURVIVAL); initialise(minecraftServer); } else { skinTracker = null; } } @Override protected void a(double d0, boolean flag, IBlockData block, BlockPosition blockposition) { if (npc == null || !npc.isFlyable()) { super.a(d0, flag, block, blockposition); } } @Override public boolean a(EntityPlayer entityplayer) { if (npc != null && !isTracked) { return false; } return super.a(entityplayer); } @Override public void a(float f, float f1, float f2) { if (npc == null || !npc.isFlyable()) { super.a(f, f1, f2); } else { NMSImpl.flyingMoveLogic(this, f, f1, f2); } } public float a(PathType pathtype) { return this.bz.containsKey(pathtype) ? this.bz.get(pathtype).floatValue() : pathtype.a(); } public void a(PathType pathtype, float f) { this.bz.put(pathtype, Float.valueOf(f)); } @Override public void c(float f, float f1) { if (npc == null || !npc.isFlyable()) { super.c(f, f1); } } @Override public void collide(net.minecraft.server.v1_13_R2.Entity entity) { // this method is called by both the entities involved - cancelling // it will not stop the NPC from moving. super.collide(entity); if (npc != null) { Util.callCollisionEvent(npc, entity.getBukkitEntity()); } } @Override public boolean damageEntity(DamageSource damagesource, float f) { // knock back velocity is cancelled and sent to client for handling when // the entity is a player. there is no client so make this happen // manually. boolean damaged = super.damageEntity(damagesource, f); if (damaged && velocityChanged) { velocityChanged = false; Bukkit.getScheduler().runTask(CitizensAPI.getPlugin(), new Runnable() { @Override public void run() { EntityHumanNPC.this.velocityChanged = true; } }); } return damaged; } @Override public void die() { super.die(); getAdvancementData().a(); } @Override public void die(DamageSource damagesource) { // players that die are not normally removed from the world. when the // NPC dies, we are done with the instance and it should be removed. if (dead) { return; } super.die(damagesource); Bukkit.getScheduler().runTaskLater(CitizensAPI.getPlugin(), new Runnable() { @Override public void run() { world.removeEntity(EntityHumanNPC.this); } }, 35); // give enough time for death and smoke animation } @Override public void enderTeleportTo(double d0, double d1, double d2) { if (npc == null) { super.enderTeleportTo(d0, d1, d2); return; } NPCEnderTeleportEvent event = new NPCEnderTeleportEvent(npc); Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { super.enderTeleportTo(d0, d1, d2); } } @Override public void f(double x, double y, double z) { if (npc == null) { super.f(x, y, z); return; } if (NPCPushEvent.getHandlerList().getRegisteredListeners().length == 0) { if (!npc.data().get(NPC.DEFAULT_PROTECTED_METADATA, true)) { super.f(x, y, z); } return; } Vector vector = new Vector(x, y, z); NPCPushEvent event = Util.callPushEvent(npc, vector); if (!event.isCancelled()) { vector = event.getCollisionVector(); super.f(vector.getX(), vector.getY(), vector.getZ()); } // when another entity collides, this method is called to push the // NPC so we prevent it from doing anything if the event is // cancelled. } @Override public CraftPlayer getBukkitEntity() { if (npc != null && !(bukkitEntity instanceof NPCHolder)) { bukkitEntity = new PlayerNPC(this); } return super.getBukkitEntity(); } public PlayerControllerJump getControllerJump() { return controllerJump; } public PlayerControllerMove getControllerMove() { return controllerMove; } public NavigationAbstract getNavigation() { return navigation; } @Override public NPC getNPC() { return npc; } @Override public IChatBaseComponent getPlayerListName() { if (Setting.REMOVE_PLAYERS_FROM_PLAYER_LIST.asBoolean()) { return new ChatComponentText(""); } return super.getPlayerListName(); } @Override public String getSkinName() { MetadataStore meta = npc.data(); String skinName = meta.get(NPC.PLAYER_SKIN_UUID_METADATA); if (skinName == null) { skinName = ChatColor.stripColor(getName()); } return skinName.toLowerCase(); } @Override public SkinPacketTracker getSkinTracker() { return skinTracker; } private void initialise(MinecraftServer minecraftServer) { Socket socket = new EmptySocket(); NetworkManager conn = null; try { conn = new EmptyNetworkManager(EnumProtocolDirection.CLIENTBOUND); playerConnection = new EmptyNetHandler(minecraftServer, conn, this); conn.setPacketListener(playerConnection); socket.close(); } catch (IOException e) { // swallow } AttributeInstance range = getAttributeInstance(GenericAttributes.FOLLOW_RANGE); if (range == null) { range = getAttributeMap().b(GenericAttributes.FOLLOW_RANGE); } range.setValue(Setting.DEFAULT_PATHFINDING_RANGE.asDouble()); controllerJump = new PlayerControllerJump(this); controllerLook = new PlayerControllerLook(this); controllerMove = new PlayerControllerMove(this); navigation = new PlayerNavigation(this, world); NMS.setStepHeight(getBukkitEntity(), 1); // the default (0) breaks step climbing setSkinFlags((byte) 0xFF); EmptyAdvancementDataPlayer.clear(this.getAdvancementData()); try { ADVANCEMENT_DATA_PLAYER.set(this, new EmptyAdvancementDataPlayer(minecraftServer, CitizensAPI.getDataFolder().getParentFile(), this)); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } @Override public boolean isCollidable() { return npc == null ? super.isCollidable() : npc.data().get(NPC.COLLIDABLE_METADATA, true); } public boolean isNavigating() { return npc.getNavigator().isNavigating(); } public void livingEntityBaseTick() { W(); this.aF = this.aG; this.aM = this.aN; if (this.hurtTicks > 0) { this.hurtTicks -= 1; } tickPotionEffects(); this.bc = this.bb; this.aR = this.aQ; this.aT = this.aS; this.lastYaw = this.yaw; this.lastPitch = this.pitch; } private void moveOnCurrentHeading() { if (bg) { if (onGround && jumpTicks == 0) { cH(); jumpTicks = 10; } } else { jumpTicks = 0; } bh *= 0.98F; bj *= 0.98F; bk *= 0.9F; a(bh, bi, bj); // movement method NMS.setHeadYaw(getBukkitEntity(), yaw); if (jumpTicks > 0) { jumpTicks--; } } public void setMoveDestination(double x, double y, double z, double speed) { controllerMove.a(x, y, z, speed); } public void setShouldJump() { controllerJump.a(); } @Override public void setSkinFlags(byte flags) { // set skin flag byte getDataWatcher().set(EntityHuman.bx, flags); } @Override public void setSkinName(String name) { setSkinName(name, false); } @Override public void setSkinName(String name, boolean forceUpdate) { Preconditions.checkNotNull(name); npc.data().setPersistent(NPC.PLAYER_SKIN_UUID_METADATA, name.toLowerCase()); skinTracker.notifySkinChange(forceUpdate); } @Override public void setSkinPersistent(String skinName, String signature, String data) { Preconditions.checkNotNull(skinName); Preconditions.checkNotNull(signature); Preconditions.checkNotNull(data); npc.data().setPersistent(NPC.PLAYER_SKIN_UUID_METADATA, skinName.toLowerCase()); npc.data().setPersistent(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_SIGN_METADATA, signature); npc.data().setPersistent(NPC.PLAYER_SKIN_TEXTURE_PROPERTIES_METADATA, data); npc.data().setPersistent(NPC.PLAYER_SKIN_USE_LATEST, false); npc.data().setPersistent("cached-skin-uuid-name", skinName.toLowerCase()); skinTracker.notifySkinChange(false); } public void setTargetLook(Entity target, float yawOffset, float renderOffset) { controllerLook.a(target, yawOffset, renderOffset); } public void setTargetLook(Location target) { controllerLook.a(target.getX(), target.getY(), target.getZ(), 10, 40); } public void setTracked() { isTracked = true; } @Override public void tick() { super.tick(); if (npc == null) return; this.noclip = isSpectator(); if (updateCounter + 1 > Setting.PACKET_UPDATE_DELAY.asInt()) { updateEffects = true; } Bukkit.getServer().getPluginManager().unsubscribeFromPermission("bukkit.broadcast.user", bukkitEntity); livingEntityBaseTick(); boolean navigating = npc.getNavigator().isNavigating(); updatePackets(navigating); if (!navigating && getBukkitEntity() != null && npc.getTrait(Gravity.class).hasGravity() && Util.isLoaded(getBukkitEntity().getLocation(LOADED_LOCATION))) { a(0, 0, 0); } if (Math.abs(motX) < EPSILON && Math.abs(motY) < EPSILON && Math.abs(motZ) < EPSILON) { motX = motY = motZ = 0; } if (navigating) { if (!NMSImpl.isNavigationFinished(navigation)) { NMSImpl.updateNavigation(navigation); } moveOnCurrentHeading(); } NMSImpl.updateAI(this); if (noDamageTicks > 0) { --noDamageTicks; } npc.update(); } public void updateAI() { controllerMove.a(); controllerLook.a(); controllerJump.b(); } private void updatePackets(boolean navigating) { if (updateCounter++ <= Setting.PACKET_UPDATE_DELAY.asInt()) return; updateCounter = 0; Location current = getBukkitEntity().getLocation(packetLocationCache); Packet[] packets = new Packet[navigating ? EnumItemSlot.values().length : EnumItemSlot.values().length + 1]; if (!navigating) { packets[5] = new PacketPlayOutEntityHeadRotation(this, (byte) MathHelper.d(NMSImpl.getHeadYaw(this) * 256.0F / 360.0F)); } int i = 0; for (EnumItemSlot slot : EnumItemSlot.values()) { packets[i++] = new PacketPlayOutEntityEquipment(getId(), slot, getEquipment(slot)); } NMSImpl.sendPacketsNearby(getBukkitEntity(), current, packets); } public void updatePathfindingRange(float pathfindingRange) { this.navigation.setRange(pathfindingRange); } @Override public boolean z_() { if (npc == null || !npc.isFlyable()) { return super.z_(); } else { return false; } } public static class PlayerNPC extends CraftPlayer implements NPCHolder, SkinnableEntity { private final CraftServer cserver; private final CitizensNPC npc; private PlayerNPC(EntityHumanNPC entity) { super((CraftServer) Bukkit.getServer(), entity); this.npc = entity.npc; this.cserver = (CraftServer) Bukkit.getServer(); npc.getTrait(Inventory.class); } @Override public Player getBukkitEntity() { return this; } @Override public EntityHumanNPC getHandle() { return (EntityHumanNPC) this.entity; } @Override public List getMetadata(String metadataKey) { return cserver.getEntityMetadata().getMetadata(this, metadataKey); } @Override public NPC getNPC() { return npc; } @Override public String getSkinName() { return ((SkinnableEntity) this.entity).getSkinName(); } @Override public SkinPacketTracker getSkinTracker() { return ((SkinnableEntity) this.entity).getSkinTracker(); } @Override public boolean hasMetadata(String metadataKey) { return cserver.getEntityMetadata().hasMetadata(this, metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) { cserver.getEntityMetadata().removeMetadata(this, metadataKey, owningPlugin); } @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { cserver.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue); } @Override public void setSkinFlags(byte flags) { ((SkinnableEntity) this.entity).setSkinFlags(flags); } @Override public void setSkinName(String name) { ((SkinnableEntity) this.entity).setSkinName(name); } @Override public void setSkinName(String skinName, boolean forceUpdate) { ((SkinnableEntity) this.entity).setSkinName(skinName, forceUpdate); } @Override public void setSkinPersistent(String skinName, String signature, String data) { ((SkinnableEntity) this.entity).setSkinPersistent(skinName, signature, data); } } private static Field ADVANCEMENT_DATA_PLAYER = NMS.getField(EntityPlayer.class, "cf"); private static final float EPSILON = 0.005F; private static final Location LOADED_LOCATION = new Location(null, 0, 0, 0); static { Field modifiersField = NMS.getField(Field.class, "modifiers"); try { modifiersField.setInt(ADVANCEMENT_DATA_PLAYER, ADVANCEMENT_DATA_PLAYER.getModifiers() & ~Modifier.FINAL); } catch (Exception e) { } } }