Created
June 6, 2015 17:29
-
-
Save Aaron1011/9e08572fb5a20ebf17c2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * This file is part of Sponge, licensed under the MIT License (MIT). | |
| * | |
| * Copyright (c) SpongePowered <https://www.spongepowered.org> | |
| * Copyright (c) contributors | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| * THE SOFTWARE. | |
| */ | |
| package org.spongepowered.common.entity.living.human; | |
| import com.google.common.base.Optional; | |
| import com.google.common.cache.CacheBuilder; | |
| import com.google.common.cache.CacheLoader; | |
| import com.google.common.cache.LoadingCache; | |
| import com.google.common.collect.Maps; | |
| import com.mojang.authlib.GameProfile; | |
| import com.mojang.authlib.properties.PropertyMap; | |
| import net.minecraft.enchantment.EnchantmentHelper; | |
| import net.minecraft.entity.Entity; | |
| import net.minecraft.entity.EntityCreature; | |
| import net.minecraft.entity.EntityLivingBase; | |
| import net.minecraft.entity.SharedMonsterAttributes; | |
| import net.minecraft.entity.ai.EntityAIAttackOnCollide; | |
| import net.minecraft.entity.ai.EntityAIBase; | |
| import net.minecraft.entity.ai.EntityAINearestAttackableTarget; | |
| import net.minecraft.entity.ai.EntityAIOpenDoor; | |
| import net.minecraft.entity.ai.EntityAISwimming; | |
| import net.minecraft.entity.ai.EntityAITempt; | |
| import net.minecraft.entity.monster.EntityMob; | |
| import net.minecraft.entity.player.EntityPlayer; | |
| import net.minecraft.entity.player.EntityPlayerMP; | |
| import net.minecraft.init.Items; | |
| import net.minecraft.item.Item; | |
| import net.minecraft.item.ItemStack; | |
| import net.minecraft.nbt.NBTTagCompound; | |
| import net.minecraft.network.Packet; | |
| import net.minecraft.network.play.server.S0CPacketSpawnPlayer; | |
| import net.minecraft.network.play.server.S13PacketDestroyEntities; | |
| import net.minecraft.network.play.server.S38PacketPlayerListItem; | |
| import net.minecraft.pathfinding.PathNavigate; | |
| import net.minecraft.pathfinding.PathNavigateGround; | |
| import net.minecraft.server.MinecraftServer; | |
| import net.minecraft.util.BlockPos; | |
| import net.minecraft.util.DamageSource; | |
| import net.minecraft.util.MathHelper; | |
| import net.minecraft.world.World; | |
| import net.minecraft.world.WorldSettings; | |
| import org.spongepowered.api.data.DataTransactionResult; | |
| import org.spongepowered.api.data.manipulator.entity.SkinData; | |
| import org.spongepowered.api.entity.ArmorEquipable; | |
| import org.spongepowered.common.data.DataTransactionBuilder; | |
| import org.spongepowered.common.data.manipulator.entity.SpongeSkinData; | |
| import org.spongepowered.common.interfaces.IMixinEntity; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.UUID; | |
| import java.util.concurrent.TimeUnit; | |
| /* | |
| * Notes | |
| * | |
| * The label above the player's head is always visible unless the human is in | |
| * a team with invisible labels set to true. Could we leverage this at all? | |
| * | |
| * Hostile mobs don't attack the human, should this be default behaviour? | |
| */ | |
| public class EntityHuman extends EntityCreature { | |
| // According to http://wiki.vg/Mojang_API#UUID_-.3E_Profile_.2B_Skin.2FCape | |
| // you can access this data once per minute, lets cache for 2 minutes | |
| private static final LoadingCache<UUID, PropertyMap> PROPERTIES_CACHE = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES) | |
| .build(new CacheLoader<UUID, PropertyMap>() { | |
| @Override | |
| public PropertyMap load(UUID uuid) throws Exception { | |
| return MinecraftServer.getServer().getMinecraftSessionService().fillProfileProperties(new GameProfile(uuid, ""), true) | |
| .getProperties(); | |
| } | |
| }); | |
| // A queue of packets waiting to send to players tracking this human | |
| private final Map<UUID, List<Packet[]>> playerPacketMap = Maps.newHashMap(); | |
| private GameProfile fakeProfile; | |
| private UUID skinUuid; | |
| private EntityLivingBase owner; | |
| public EntityHuman(World worldIn) { | |
| super(worldIn); | |
| this.fakeProfile = new GameProfile(this.entityUniqueID, ""); | |
| this.setCanPickUpLoot(true); | |
| this.setSize(0.6F, 1.8F); | |
| ((PathNavigateGround)this.getNavigator()).setCanSwim(true); | |
| ((PathNavigateGround)this.getNavigator()).setEnterDoors(true); | |
| ((PathNavigateGround)this.getNavigator()).setBreakDoors(true); | |
| this.targetTasks.addTask(1, new EntityAINearestAttackableTarget(this, EntityMob.class, false)); | |
| this.tasks.addTask(1, new EntityAIAttackOnCollide(this, EntityMob.class, 1D, false)); | |
| this.tasks.addTask(2, new EntityAIFollow(this, 4D, 3.5f)); | |
| //this.tasks.addTask(2, new EntityAITempt(this, 5D, Items.wheat, false)); | |
| this.tasks.addTask(3, new EntityAIOpenDoor(this, true)); | |
| this.tasks.addTask(4, new EntityAISwimming(this)); | |
| //this.tasks.addTask(5, new EntityAIWatchClosest(this, EntityPlayer.class, 6.0F, 2)); | |
| } | |
| @Override | |
| protected void applyEntityAttributes() { | |
| super.applyEntityAttributes(); | |
| this.getAttributeMap().registerAttribute(SharedMonsterAttributes.attackDamage).setBaseValue(1.0D); | |
| this.getAttributeMap().getAttributeInstance(SharedMonsterAttributes.movementSpeed).setBaseValue(0.3D); | |
| } | |
| @Override | |
| protected void entityInit() { | |
| super.entityInit(); | |
| this.dataWatcher.addObject(16, (byte) 0); | |
| this.dataWatcher.addObject(17, 0.0F); | |
| this.dataWatcher.addObject(18, 0); | |
| // Enables all skin features | |
| this.dataWatcher.addObject(10, (byte) 0xFF); | |
| } | |
| @Override | |
| public void setCustomNameTag(String name) { | |
| if (name.length() > 16) { | |
| // Vanilla restriction | |
| name = name.substring(0, 16); | |
| } | |
| if (this.getCustomNameTag().equals(name)) { | |
| return; | |
| } | |
| super.setCustomNameTag(name); | |
| this.renameProfile(name); | |
| if (this.isAliveAndInWorld()) { | |
| this.respawnOnClient(); | |
| } | |
| } | |
| @Override | |
| public void readEntityFromNBT(NBTTagCompound tagCompund) { | |
| super.readEntityFromNBT(tagCompund); | |
| String skinUuidString = ((IMixinEntity) this).getSpongeData().getString("skinUuid"); | |
| if (!skinUuidString.isEmpty()) { | |
| this.updateFakeProfileWithSkin(UUID.fromString(skinUuidString)); | |
| } | |
| } | |
| @Override | |
| public void writeEntityToNBT(NBTTagCompound tagCompound) { | |
| super.writeEntityToNBT(tagCompound); | |
| if (this.skinUuid != null) { | |
| ((IMixinEntity) this).getSpongeData().setString("skinUuid", this.skinUuid.toString()); | |
| } | |
| } | |
| @Override | |
| public void onLivingUpdate() { | |
| super.onLivingUpdate(); | |
| this.updateArmSwingProgress(); | |
| } | |
| @Override | |
| public int getMaxInPortalTime() { | |
| return 80; | |
| } | |
| @Override | |
| protected String getSwimSound() { | |
| return "game.player.swim"; | |
| } | |
| @Override | |
| protected String getSplashSound() { | |
| return "game.player.swim.splash"; | |
| } | |
| @Override | |
| public int getPortalCooldown() { | |
| return 10; | |
| } | |
| @Override | |
| public void onDeath(DamageSource cause) { | |
| super.onDeath(cause); | |
| this.setSize(0.2F, 0.2F); | |
| this.setPosition(this.posX, this.posY, this.posZ); | |
| this.motionY = 0.1D; | |
| if (cause != null) { | |
| this.motionX = (double) (-MathHelper.cos((this.attackedAtYaw + this.rotationYaw) * (float) Math.PI / 180.0F) * 0.1F); | |
| this.motionZ = (double) (-MathHelper.sin((this.attackedAtYaw + this.rotationYaw) * (float) Math.PI / 180.0F) * 0.1F); | |
| } else { | |
| this.motionX = this.motionZ = 0.0D; | |
| } | |
| } | |
| @Override | |
| protected String getHurtSound() { | |
| return "game.player.hurt"; | |
| } | |
| @Override | |
| protected String getDeathSound() { | |
| return "game.player.die"; | |
| } | |
| @Override | |
| public double getYOffset() { | |
| return -0.35D; | |
| } | |
| @Override | |
| public float getAIMoveSpeed() { | |
| return (float) this.getEntityAttribute(SharedMonsterAttributes.movementSpeed).getAttributeValue(); | |
| } | |
| @Override | |
| protected String getFallSoundString(int damageValue) { | |
| return damageValue > 4 ? "game.player.hurt.fall.big" : "game.player.hurt.fall.small"; | |
| } | |
| @Override | |
| public float getEyeHeight() { | |
| return 1.62f; | |
| } | |
| @Override | |
| public float getAbsorptionAmount() { | |
| return this.getDataWatcher().getWatchableObjectFloat(17); | |
| } | |
| @Override | |
| public void setAbsorptionAmount(float amount) { | |
| if (amount < 0.0F) { | |
| amount = 0.0F; | |
| } | |
| this.getDataWatcher().updateObject(17, amount); | |
| } | |
| @Override | |
| protected float func_110146_f(float p_110146_1_, float p_110146_2_) { | |
| float retValue = super.func_110146_f(p_110146_1_, p_110146_2_); | |
| // Make the body rotation follow head rotation | |
| this.rotationYaw = this.rotationYawHead; | |
| return retValue; | |
| } | |
| @Override | |
| public boolean attackEntityAsMob(Entity entityIn) { | |
| super.attackEntityAsMob(entityIn); | |
| swingItem(); | |
| float f = (float) this.getEntityAttribute(SharedMonsterAttributes.attackDamage).getAttributeValue(); | |
| int i = 0; | |
| if (entityIn instanceof EntityLivingBase) { | |
| f += EnchantmentHelper.func_152377_a(this.getHeldItem(), ((EntityLivingBase) entityIn).getCreatureAttribute()); | |
| i += EnchantmentHelper.getKnockbackModifier(this); | |
| } | |
| boolean flag = entityIn.attackEntityFrom(DamageSource.causeMobDamage(this), f); | |
| if (flag) { | |
| if (i > 0) { | |
| entityIn.addVelocity((double) (-MathHelper.sin(this.rotationYaw * (float) Math.PI / 180.0F) * (float) i * 0.5F), 0.1D, | |
| (double) (MathHelper.cos(this.rotationYaw * (float) Math.PI / 180.0F) * (float) i * 0.5F)); | |
| this.motionX *= 0.6D; | |
| this.motionZ *= 0.6D; | |
| } | |
| int j = EnchantmentHelper.getFireAspectModifier(this); | |
| if (j > 0) { | |
| entityIn.setFire(j * 4); | |
| } | |
| this.func_174815_a(this, entityIn); | |
| } | |
| return flag; | |
| } | |
| private void renameProfile(String newName) { | |
| PropertyMap props = this.fakeProfile.getProperties(); | |
| this.fakeProfile = new GameProfile(this.fakeProfile.getId(), newName); | |
| this.fakeProfile.getProperties().putAll(props); | |
| } | |
| private boolean updateFakeProfileWithSkin(UUID skin) { | |
| PropertyMap properties = PROPERTIES_CACHE.getUnchecked(skin); | |
| if (properties.isEmpty()) { | |
| return false; | |
| } | |
| this.fakeProfile.getProperties().replaceValues("textures", properties.get("textures")); | |
| this.skinUuid = skin; | |
| return true; | |
| } | |
| public DataTransactionResult setSkinData(SkinData skin) { | |
| if (!MinecraftServer.getServer().isServerInOnlineMode()) { | |
| // Skins only work when online-mode = true | |
| return DataTransactionBuilder.fail(skin); | |
| } | |
| if (skin.getValue().equals(this.skinUuid)) { | |
| return DataTransactionBuilder.successNoData(); | |
| } | |
| if (!updateFakeProfileWithSkin(skin.getValue())) { | |
| return DataTransactionBuilder.fail(skin); | |
| } | |
| if (this.isAliveAndInWorld()) { | |
| this.respawnOnClient(); | |
| } | |
| return DataTransactionBuilder.successNoData(); | |
| } | |
| public Optional<SkinData> getSkinData() { | |
| if (this.skinUuid != null) { | |
| return Optional.<SkinData>of(new SpongeSkinData(this.skinUuid)); | |
| } | |
| return Optional.absent(); | |
| } | |
| public SkinData createSkinData() { | |
| return this.getSkinData().or(new SpongeSkinData(this.entityUniqueID)); | |
| } | |
| public boolean removeSkin() { | |
| if (this.skinUuid == null) { | |
| return false; | |
| } | |
| this.fakeProfile.getProperties().removeAll("textures"); | |
| this.skinUuid = null; | |
| if (this.isAliveAndInWorld()) { | |
| this.respawnOnClient(); | |
| } | |
| return true; | |
| } | |
| public Optional<SkinData> fillSkinData(SkinData manipulator) { | |
| if (this.skinUuid == null) { | |
| return Optional.absent(); | |
| } | |
| return Optional.of(manipulator.setValue(this.skinUuid)); | |
| } | |
| private boolean isAliveAndInWorld() { | |
| return this.worldObj.getEntityByID(this.getEntityId()) == this && !this.isDead; | |
| } | |
| private void respawnOnClient() { | |
| this.pushPackets(new S13PacketDestroyEntities(this.getEntityId()), this.createPlayerListPacket(S38PacketPlayerListItem.Action.ADD_PLAYER)); | |
| this.pushPackets(this.createSpawnPacket()); | |
| this.pushPackets(this.createPlayerListPacket(S38PacketPlayerListItem.Action.REMOVE_PLAYER)); | |
| } | |
| /** | |
| * Can the fake profile be removed from the tab list immediately (i.e. as | |
| * soon as the human has spawned). | |
| * | |
| * @return Whether it can be removed with 0 ticks delay | |
| */ | |
| public boolean canRemoveFromListImmediately() { | |
| return !this.fakeProfile.getProperties().containsKey("textures"); | |
| } | |
| /** | |
| * Called when a player stops tracking this human. | |
| * | |
| * Removes the player from the packet queue and sends them a REMOVE_PLAYER | |
| * tab list packet to make sure the human is not on it. | |
| * | |
| * @param player The player that has stopped tracking this human | |
| */ | |
| public void onRemovedFrom(EntityPlayerMP player) { | |
| this.playerPacketMap.remove(player.getUniqueID()); | |
| player.playerNetServerHandler.sendPacket(this.createPlayerListPacket(S38PacketPlayerListItem.Action.REMOVE_PLAYER)); | |
| } | |
| /** | |
| * Creates a {@link S0CPacketSpawnPlayer} packet. | |
| * | |
| * Copied directly from the constructor of the packet, because that can't be | |
| * used as we're not an EntityPlayer. | |
| * | |
| * @return A new spawn packet | |
| */ | |
| public S0CPacketSpawnPlayer createSpawnPacket() { | |
| S0CPacketSpawnPlayer packet = new S0CPacketSpawnPlayer(); | |
| packet.entityId = this.getEntityId(); | |
| packet.playerId = this.fakeProfile.getId(); | |
| packet.x = MathHelper.floor_double(this.posX * 32.0D); | |
| packet.y = MathHelper.floor_double(this.posY * 32.0D); | |
| packet.z = MathHelper.floor_double(this.posZ * 32.0D); | |
| packet.yaw = (byte) ((int) (this.rotationYaw * 256.0F / 360.0F)); | |
| packet.pitch = (byte) ((int) (this.rotationPitch * 256.0F / 360.0F)); | |
| ItemStack itemstack = (ItemStack) ((ArmorEquipable) this).getItemInHand().orNull(); | |
| packet.currentItem = itemstack == null ? 0 : Item.getIdFromItem(itemstack.getItem()); | |
| packet.watcher = this.getDataWatcher(); | |
| return packet; | |
| } | |
| /** | |
| * Creates a {@link S38PacketPlayerListItem} packet with the given action. | |
| * | |
| * @param action The action to apply on the tab list | |
| * @return A new tab list packet | |
| */ | |
| @SuppressWarnings("unchecked") | |
| public S38PacketPlayerListItem createPlayerListPacket(S38PacketPlayerListItem.Action action) { | |
| S38PacketPlayerListItem packet = new S38PacketPlayerListItem(action); | |
| packet.field_179769_b.add(packet.new AddPlayerData(this.fakeProfile, 0, WorldSettings.GameType.NOT_SET, this.getDisplayName())); | |
| return packet; | |
| } | |
| /** | |
| * Push the given packets to all players tracking this human. | |
| * | |
| * @param packets All packets to send in a single tick | |
| */ | |
| public void pushPackets(Packet... packets) { | |
| this.pushPackets(null, packets); // null = all players | |
| } | |
| /** | |
| * Push the given packets to the given player (who must be tracking this | |
| * human). | |
| * | |
| * @param player The player tracking this human | |
| * @param packets All packets to send in a single tick | |
| */ | |
| public void pushPackets(EntityPlayerMP player, Packet... packets) { | |
| List<Packet[]> queue = this.playerPacketMap.get(player); | |
| if (queue == null) { | |
| this.playerPacketMap.put(player == null ? null : player.getUniqueID(), queue = new ArrayList<Packet[]>()); | |
| } | |
| queue.add(packets); | |
| } | |
| /** | |
| * (Internal) Pops the packets off the queue for the given player. | |
| * | |
| * @param player The player to get packets for (or null for all players) | |
| * @param player The player to get packets for (or null for alln players) | |
| * @return An array of packets to send in a single tick | |
| */ | |
| public Packet[] popQueuedPackets(EntityPlayerMP player) { | |
| List<Packet[]> queue = this.playerPacketMap.get(player == null ? null : player.getUniqueID()); | |
| return queue == null || queue.isEmpty() ? null : queue.remove(0); | |
| } | |
| @Override | |
| protected boolean interact(EntityPlayer player) { | |
| if (player == this.owner) { | |
| if (this.getHeldItem() == player.getHeldItem()) { | |
| return false; | |
| } | |
| this.setCurrentItemOrArmor(0, player.getHeldItem()); | |
| return true; | |
| } | |
| return false; | |
| } | |
| public EntityLivingBase getOwner() { | |
| return this.owner; | |
| } | |
| public void setOwner(EntityLivingBase owner) { | |
| this.owner = owner; | |
| } | |
| public class EntityAIFollow extends EntityAIBase { | |
| private EntityHuman theFollower; | |
| World theWorld; | |
| private double speed; | |
| private PathNavigate petPathfinder; | |
| private int field_75343_h; | |
| float maxDist; | |
| float minDist; | |
| private boolean field_75344_i; | |
| private static final String __OBFID = "CL_00001585"; | |
| public EntityAIFollow(EntityHuman theFollower, double p_i1625_2_, float minDistIn) { | |
| this.theFollower = theFollower; | |
| this.theWorld = theFollower.worldObj; | |
| this.speed = p_i1625_2_; | |
| this.petPathfinder = theFollower.getNavigator(); | |
| this.minDist = minDistIn; | |
| this.setMutexBits(3); | |
| if (!(theFollower.getNavigator() instanceof PathNavigateGround)) { | |
| throw new IllegalArgumentException("Unsupported mob type for FollowOwnerGoal"); | |
| } | |
| } | |
| /** | |
| * Returns whether the EntityAIBase should begin execution. | |
| */ | |
| public boolean shouldExecute() { | |
| return theFollower.getOwner() != null; | |
| } | |
| /** | |
| * Returns whether an in-progress EntityAIBase should continue executing | |
| */ | |
| public boolean continueExecuting() { | |
| return theFollower.getOwner() != null; | |
| } | |
| /** | |
| * Execute a one shot task or start executing a continuous task | |
| */ | |
| public void startExecuting() { | |
| this.field_75343_h = 0; | |
| this.field_75344_i = ((PathNavigateGround)this.theFollower.getNavigator()).getAvoidsWater(); | |
| ((PathNavigateGround)this.theFollower.getNavigator()).setAvoidsWater(false); | |
| } | |
| /** | |
| * Resets the task | |
| */ | |
| public void resetTask() { | |
| //this.theFollower.setOwner(null); | |
| this.petPathfinder.clearPathEntity(); | |
| ((PathNavigateGround)this.theFollower.getNavigator()).setAvoidsWater(true); | |
| } | |
| /** | |
| * Updates the task | |
| */ | |
| public void updateTask() { | |
| this.theFollower.getLookHelper() | |
| .setLookPositionWithEntity(this.theFollower.getOwner(), 30.0F, (float) this.theFollower.getVerticalFaceSpeed()); | |
| if (this.theFollower.getDistanceSqToEntity(this.theFollower.getOwner()) < (this.minDist * this.minDist)) { | |
| this.theFollower.getNavigator().clearPathEntity(); | |
| } else { | |
| double range = this.theFollower.getAttributeMap().getAttributeInstance(SharedMonsterAttributes.followRange).getAttributeValue(); | |
| if (this.theFollower.getDistanceSqToEntity(this.theFollower.getOwner()) > (range * range)) { | |
| int i = MathHelper.floor_double(this.theFollower.getOwner().posX) - 2; | |
| int j = MathHelper.floor_double(this.theFollower.getOwner().posZ) - 2; | |
| int k = MathHelper.floor_double(this.theFollower.getOwner().getEntityBoundingBox().minY); | |
| for (int l = 0; l <= 4; ++l) | |
| { | |
| for (int i1 = 0; i1 <= 4; ++i1) | |
| { | |
| if (World.doesBlockHaveSolidTopSurface(this.theWorld, new BlockPos(i + l, k - 1, j + i1)) && !this.theWorld.getBlockState(new BlockPos(i + l, k, j + i1)).getBlock().getMaterial().blocksMovement() && !this.theWorld.getBlockState(new BlockPos(i + l, k + 1, j + i1)).getBlock().getMaterial().blocksMovement()) | |
| { | |
| this.theFollower.setLocationAndAngles((double)((float)(i + l) + 0.5F), (double)k, (double)((float)(j + i1) + 0.5F), this.theFollower.rotationYaw, this.theFollower.rotationPitch); | |
| this.petPathfinder.clearPathEntity(); | |
| return; | |
| } | |
| } | |
| } | |
| } else { | |
| this.theFollower.getNavigator().tryMoveToEntityLiving(this.theFollower.getOwner(), this.speed); | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment