package openblocks.common.entity; import com.google.common.base.Objects; import com.google.common.base.Throwables; import com.google.common.collect.Iterables; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mojang.authlib.properties.Property; import cpw.mods.fml.common.eventhandler.SubscribeEvent; import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.Map; import java.util.UUID; import net.minecraft.client.Minecraft; import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityCreature; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.EntityAILookIdle; import net.minecraft.entity.ai.EntityAISwimming; import net.minecraft.entity.ai.EntityAIWander; import net.minecraft.entity.ai.EntityAIWatchClosest; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTUtil; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.common.util.Constants; import openblocks.common.entity.ai.EntityAIBreakBlock; import openblocks.common.entity.ai.EntityAIPickupPlayer; import openmods.Log; import openmods.api.VisibleForDocumentation; import openmods.network.event.EventDirection; import openmods.network.event.NetworkEvent; import openmods.network.event.NetworkEventMeta; import openmods.utils.ByteUtils; import openmods.utils.io.GameProfileSerializer; @VisibleForDocumentation public class EntityMiniMe extends EntityCreature implements IEntityAdditionalSpawnData { @NetworkEventMeta(direction = EventDirection.S2C, compressed = true) public static class OwnerChangeEvent extends NetworkEvent { private GameProfile profile; private int entityId; public OwnerChangeEvent(int entityId, GameProfile profile) { this.profile = profile; this.entityId = entityId; } @Override protected void readFromStream(DataInput input) throws IOException { this.entityId = ByteUtils.readVLI(input); if (input.readBoolean()) { profile = GameProfileSerializer.read(input); } } @Override protected void writeToStream(DataOutput output) throws IOException { ByteUtils.writeVLI(output, entityId); if (profile != null) { output.writeBoolean(true); GameProfileSerializer.write(profile, output); } else { output.writeBoolean(false); } } } public static class OwnerChangeHandler { @SubscribeEvent public void onProfileChange(OwnerChangeEvent evt) { final World world = evt.sender.worldObj; Entity e = world.getEntityByID(evt.entityId); if (e instanceof EntityMiniMe) { ((EntityMiniMe)e).owner = evt.profile; } } } @SideOnly(Side.CLIENT) private ResourceLocation locationSkin; private GameProfile owner; private int pickupCooldown = 0; private boolean wasRidden = false; public EntityMiniMe(World world, GameProfile owner) { this(world); this.owner = owner != null? fetchFullProfile(owner) : null; } public EntityMiniMe(World world) { super(world); setSize(0.6F, 0.95F); func_110163_bv(); getNavigator().setAvoidsWater(true); getNavigator().setCanSwim(true); this.tasks.addTask(1, new EntityAISwimming(this)); this.tasks.addTask(2, new EntityAIPickupPlayer(this)); this.tasks.addTask(3, new EntityAIBreakBlock(this)); this.tasks.addTask(4, new EntityAIWander(this, 1.0D)); this.tasks.addTask(5, new EntityAIWatchClosest(this, EntityPlayer.class, 6.0F)); this.tasks.addTask(6, new EntityAILookIdle(this)); } @Override public void onEntityUpdate() { super.onEntityUpdate(); if (pickupCooldown > 0) pickupCooldown--; if (wasRidden && riddenByEntity == null) { wasRidden = false; setPickupCooldown(1200); } else if (riddenByEntity != null) { wasRidden = true; } } @Override public double getMountedYOffset() { return height + 0.15; } public int getPickupCooldown() { return pickupCooldown; } public void setPickupCooldown(int cooldown) { pickupCooldown = cooldown; } @Override protected void applyEntityAttributes() { super.applyEntityAttributes(); getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(10.0D); getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.3D); } @SideOnly(Side.CLIENT) public ResourceLocation getLocationSkin() { return Objects.firstNonNull(getResourceLocation(), AbstractClientPlayer.locationStevePng); } @Override public void setCustomNameTag(String name) { super.setCustomNameTag(name); if (!worldObj.isRemote && MinecraftServer.getServer() != null) { if (name != null && (owner == null || !name.equalsIgnoreCase(owner.getName()))) { try { final GameProfile profile = MinecraftServer.getServer().func_152358_ax().func_152655_a(name); this.owner = profile != null? fetchFullProfile(profile) : null; propagateOwnerChange(); } catch (Exception e) { Log.warn(e, "Failed to change skin to %s", name); } } } } private void propagateOwnerChange() { new OwnerChangeEvent(getEntityId(), owner).sendToEntity(this); } private ResourceLocation getResourceLocation() { if (owner != null) { Minecraft minecraft = Minecraft.getMinecraft(); Map<?, ?> map = minecraft.func_152342_ad().func_152788_a(owner); if (map.containsKey(Type.SKIN)) { final MinecraftProfileTexture skin = (MinecraftProfileTexture)map.get(Type.SKIN); return minecraft.func_152342_ad().func_152792_a(skin, Type.SKIN); } } return null; } private static GameProfile fetchFullProfile(GameProfile profile) { final Property property = Iterables.getFirst(profile.getProperties().get("textures"), null); return property != null? profile : MinecraftServer.getServer().func_147130_as().fillProfileProperties(profile, true); } @Override protected boolean canDespawn() { return false; } @Override public boolean isAIEnabled() { return true; } @Override public boolean isChild() { return true; } public GameProfile getOwner() { return owner; } @Override public void writeSpawnData(ByteBuf data) { if (owner != null) { data.writeBoolean(true); try { GameProfileSerializer.write(owner, new ByteBufOutputStream(data)); } catch (IOException e) { throw Throwables.propagate(e); } } else data.writeBoolean(false); } @Override public void readSpawnData(ByteBuf data) { if (data.readBoolean()) { try { this.owner = GameProfileSerializer.read(new ByteBufInputStream(data)); } catch (IOException e) { throw Throwables.propagate(e); } } } @Override public void writeEntityToNBT(NBTTagCompound tag) { super.writeEntityToNBT(tag); if (owner != null) { NBTTagCompound ownerTag = new NBTTagCompound(); NBTUtil.func_152460_a(ownerTag, owner); tag.setTag("Owner", ownerTag); } tag.setInteger("pickupCooldown", pickupCooldown); } @Override public void readEntityFromNBT(NBTTagCompound tag) { this.owner = readOwner(tag); // switched order, to prevent needless profile fetch in setCustomName super.readEntityFromNBT(tag); this.pickupCooldown = tag.getInteger("pickupCooldown"); } private static GameProfile readOwner(NBTTagCompound tag) { if (tag.hasKey("owner", Constants.NBT.TAG_STRING)) { String ownerName = tag.getString("owner"); return MinecraftServer.getServer().func_152358_ax().func_152655_a(ownerName); } else if (tag.hasKey("OwnerUUID", Constants.NBT.TAG_STRING)) { final String uuidStr = tag.getString("OwnerUUID"); try { UUID uuid = UUID.fromString(uuidStr); return new GameProfile(uuid, null); } catch (IllegalArgumentException e) { Log.warn(e, "Failed to parse UUID: %s", uuidStr); } } else if (tag.hasKey("Owner", Constants.NBT.TAG_COMPOUND)) { return NBTUtil.func_152459_a(tag.getCompoundTag("Owner")); } return null; } }