Skip to content

Instantly share code, notes, and snippets.

@Aaron1011
Created June 6, 2015 17:29
Show Gist options
  • Select an option

  • Save Aaron1011/9e08572fb5a20ebf17c2 to your computer and use it in GitHub Desktop.

Select an option

Save Aaron1011/9e08572fb5a20ebf17c2 to your computer and use it in GitHub Desktop.
/*
* 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