| package net.glowstone.io.entity; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.UUID; |
| import java.util.stream.Collectors; |
| import net.glowstone.entity.AttributeManager; |
| import net.glowstone.entity.AttributeManager.Property; |
| import net.glowstone.entity.GlowLivingEntity; |
| import net.glowstone.entity.objects.GlowLeashHitch; |
| import net.glowstone.io.nbt.NbtSerialization; |
| import net.glowstone.util.InventoryUtil; |
| import net.glowstone.util.nbt.CompoundTag; |
| import org.bukkit.Location; |
| import org.bukkit.attribute.AttributeModifier; |
| import org.bukkit.entity.Entity; |
| import org.bukkit.entity.EntityType; |
| import org.bukkit.entity.LeashHitch; |
| import org.bukkit.entity.LivingEntity; |
| import org.bukkit.entity.Player; |
| import org.bukkit.inventory.EntityEquipment; |
| import org.bukkit.inventory.ItemStack; |
| import org.bukkit.potion.PotionEffect; |
| import org.bukkit.potion.PotionEffectType; |
| |
| public abstract class LivingEntityStore<T extends GlowLivingEntity> extends EntityStore<T> { |
| |
| public LivingEntityStore(Class<T> clazz, String type) { |
| super(clazz, type); |
| } |
| |
| public LivingEntityStore(Class<T> clazz, EntityType type) { |
| super(clazz, type); |
| } |
| |
| // these tags that apply to living entities only are documented as global: |
| // - short "Air" |
| // - string "CustomName" |
| // - bool "CustomNameVisible" |
| |
| // todo: the following tags |
| // - float "AbsorptionAmount" |
| // - short "HurtTime" |
| // - int "HurtByTimestamp" |
| // - short "DeathTime" |
| // - bool "PersistenceRequired" |
| // on ActiveEffects, bool "ShowParticles" |
| |
| @Override |
| public void load(T entity, CompoundTag compound) { |
| super.load(entity, compound); |
| compound.readShort("Air", entity::setRemainingAir); |
| compound.readString("CustomName", entity::setCustomName); |
| compound.readBoolean("CustomNameVisible", entity::setCustomNameVisible); |
| if (!compound.readFloat("HealF", entity::setHealth)) { |
| compound.readShort("Health", entity::setHealth); |
| } |
| compound.readShort("AttackTime", entity::setNoDamageTicks); |
| compound.readBoolean("FallFlying", entity::setFallFlying); |
| compound.iterateCompoundList("ActiveEffects", effect -> { |
| // should really always have every field, but be forgiving if possible |
| if (!effect.isByte("Id") || !effect.isInt("Duration")) { |
| return; |
| } |
| |
| PotionEffectType type = PotionEffectType.getById(effect.getByte("Id")); |
| int duration = effect.getInt("Duration"); |
| if (type == null || duration < 0) { |
| return; |
| } |
| final int amplifier = compound.tryGetInt("Amplifier").orElse(0); |
| boolean ambient = compound.getBoolean("Ambient", false); |
| // bool "ShowParticles" |
| |
| entity.addPotionEffect(new PotionEffect(type, duration, amplifier, ambient), true); |
| }); |
| |
| EntityEquipment equip = entity.getEquipment(); |
| if (equip != null) { |
| loadEquipment(entity, equip, compound); |
| } |
| compound.readBoolean("CanPickUpLoot", entity::setCanPickupItems); |
| AttributeManager am = entity.getAttributeManager(); |
| compound.iterateCompoundList("Attributes", tag -> { |
| if (!tag.isString("Name") || !tag.isDouble("Base")) { |
| return; |
| } |
| List<AttributeModifier> modifiers = new ArrayList<>(); |
| tag.iterateCompoundList("Modifiers", modifierTag -> { |
| if (modifierTag.isDouble("Amount") |
| && modifierTag.isString("Name") |
| && modifierTag.isInt("Operation") |
| && modifierTag.isLong("UUIDLeast") |
| && modifierTag.isLong("UUIDMost")) { |
| modifiers.add(new AttributeModifier( |
| new UUID(modifierTag.getLong("UUIDLeast"), |
| modifierTag.getLong("UUIDMost")), |
| modifierTag.getString("Name"), |
| modifierTag.getDouble("Amount"), |
| AttributeModifier.Operation.values()[modifierTag.getInt("Operation")])); |
| } |
| }); |
| AttributeManager.Key key = AttributeManager.Key.fromName(tag.getString("Name")); |
| am.setProperty(key, tag.getDouble("Base"), modifiers); |
| }); |
| Optional<CompoundTag> maybeLeash = compound.tryGetCompound("Leash"); |
| if (maybeLeash.isPresent()) { |
| CompoundTag leash = maybeLeash.get(); |
| if (!leash.readUuid("UUIDMost", "UUIDLeast", entity::setLeashHolderUniqueId) |
| && leash.isInt("X") && leash.isInt("Y") && leash.isInt("Z")) { |
| int x = leash.getInt("X"); |
| int y = leash.getInt("Y"); |
| int z = leash.getInt("Z"); |
| |
| LeashHitch leashHitch = GlowLeashHitch |
| .getLeashHitchAt(new Location(entity.getWorld(), x, y, z).getBlock()); |
| entity.setLeashHolder(leashHitch); |
| } |
| } else { |
| compound.readBoolean("Leashed", leashSet -> { |
| if (leashSet) { |
| // We know that there was something leashed, but not what entity it was |
| // This can happen, when for example Minecart got leashed |
| // We still have to make sure that we drop a Leash Item |
| entity.setLeashHolderUniqueId(UUID.randomUUID()); |
| } |
| }); |
| } |
| } |
| |
| private void loadEquipment(T entity, EntityEquipment equip, CompoundTag compound) { |
| // Deprecated since 15w31a, left here for compatibilty for now |
| compound.readCompoundList("Equipment", list -> { |
| equip.setItemInMainHand(getItem(list, 0)); |
| equip.setBoots(getItem(list, 1)); |
| equip.setLeggings(getItem(list, 2)); |
| equip.setChestplate(getItem(list, 3)); |
| equip.setHelmet(getItem(list, 4)); |
| }); |
| // Deprecated since 15w31a, left here for compatibilty for now |
| compound.readFloatList("DropChances", list -> { |
| equip.setItemInMainHandDropChance(getOrDefault(list, 0, 1f)); |
| equip.setBootsDropChance(getOrDefault(list, 1, 1f)); |
| equip.setLeggingsDropChance(getOrDefault(list, 2, 1f)); |
| equip.setChestplateDropChance(getOrDefault(list, 3, 1f)); |
| equip.setHelmetDropChance(getOrDefault(list, 4, 1f)); |
| }); |
| compound.readCompoundList("HandItems", list -> { |
| equip.setItemInMainHand(getItem(list, 0)); |
| equip.setItemInOffHand(getItem(list, 1)); |
| }); |
| compound.readCompoundList("ArmorItems", list -> { |
| equip.setBoots(getItem(list, 0)); |
| equip.setLeggings(getItem(list, 1)); |
| equip.setChestplate(getItem(list, 2)); |
| equip.setHelmet(getItem(list, 3)); |
| }); |
| |
| // set of dropchances on a player throws an UnsupportedOperationException |
| if (!(entity instanceof Player)) { |
| compound.readFloatList("HandDropChances", list -> { |
| equip.setItemInMainHandDropChance(getOrDefault(list, 0, 1f)); |
| equip.setItemInOffHandDropChance(getOrDefault(list, 1, 1f)); |
| }); |
| compound.readFloatList("ArmorDropChances", list -> { |
| equip.setBootsDropChance(getOrDefault(list, 0, 1f)); |
| equip.setLeggingsDropChance(getOrDefault(list, 1, 1f)); |
| equip.setChestplateDropChance(getOrDefault(list, 2, 1f)); |
| equip.setHelmetDropChance(getOrDefault(list, 3, 1f)); |
| }); |
| } |
| } |
| |
| private ItemStack getItem(List<CompoundTag> list, int index) { |
| if (list == null) { |
| return InventoryUtil.createEmptyStack(); |
| } |
| |
| if (index >= list.size()) { |
| return InventoryUtil.createEmptyStack(); |
| } |
| |
| return NbtSerialization.readItem(list.get(index)); |
| } |
| |
| private float getOrDefault(List<Float> list, int index, float defaultValue) { |
| if (list == null) { |
| return defaultValue; |
| } |
| |
| if (index >= list.size()) { |
| return defaultValue; |
| } |
| |
| return list.get(index); |
| } |
| |
| @Override |
| public void save(T entity, CompoundTag tag) { |
| super.save(entity, tag); |
| |
| tag.putShort("Air", entity.getRemainingAir()); |
| if (entity.getCustomName() != null && !entity.getCustomName().isEmpty()) { |
| tag.putString("CustomName", entity.getCustomName()); |
| tag.putBool("CustomNameVisible", entity.isCustomNameVisible()); |
| } |
| |
| tag.putFloat("HealF", entity.getHealth()); |
| tag.putShort("Health", (int) entity.getHealth()); |
| tag.putShort("AttackTime", entity.getNoDamageTicks()); |
| tag.putBool("FallFlying", entity.isFallFlying()); |
| |
| Map<String, Property> properties = entity.getAttributeManager().getAllProperties(); |
| if (!properties.isEmpty()) { |
| List<CompoundTag> attributes = new ArrayList<>(properties.size()); |
| |
| properties.forEach((key, property) -> { |
| CompoundTag attribute = new CompoundTag(); |
| attribute.putString("Name", key); |
| attribute.putDouble("Base", property.getValue()); |
| |
| Collection<AttributeModifier> modifiers = property.getModifiers(); |
| if (modifiers != null && !modifiers.isEmpty()) { |
| List<CompoundTag> modifierTags = modifiers.stream().map(modifier -> { |
| CompoundTag modifierTag = new CompoundTag(); |
| modifierTag.putDouble("Amount", modifier.getAmount()); |
| modifierTag.putString("Name", modifier.getName()); |
| modifierTag.putInt("Operation", modifier.getOperation().ordinal()); |
| UUID uuid = modifier.getUniqueId(); |
| modifierTag.putLong("UUIDLeast", uuid.getLeastSignificantBits()); |
| modifierTag.putLong("UUIDMost", uuid.getMostSignificantBits()); |
| return modifierTag; |
| }).collect(Collectors.toList()); |
| attribute.putCompoundList("Modifiers", modifierTags); |
| } |
| |
| attributes.add(attribute); |
| }); |
| |
| tag.putCompoundList("Attributes", attributes); |
| } |
| |
| List<CompoundTag> effects = new LinkedList<>(); |
| for (PotionEffect effect : entity.getActivePotionEffects()) { |
| CompoundTag effectTag = new CompoundTag(); |
| effectTag.putByte("Id", effect.getType().getId()); |
| effectTag.putByte("Amplifier", effect.getAmplifier()); |
| effectTag.putInt("Duration", effect.getDuration()); |
| effectTag.putBool("Ambient", effect.isAmbient()); |
| effectTag.putBool("ShowParticles", true); |
| effects.add(effectTag); |
| } |
| tag.putCompoundList("ActiveEffects", effects); |
| |
| EntityEquipment equip = entity.getEquipment(); |
| if (equip != null) { |
| tag.putCompoundList("HandItems", Arrays.asList( |
| NbtSerialization.writeItem(equip.getItemInMainHand(), -1), |
| NbtSerialization.writeItem(equip.getItemInOffHand(), -1) |
| )); |
| tag.putCompoundList("ArmorItems", Arrays.asList( |
| NbtSerialization.writeItem(equip.getBoots(), -1), |
| NbtSerialization.writeItem(equip.getLeggings(), -1), |
| NbtSerialization.writeItem(equip.getChestplate(), -1), |
| NbtSerialization.writeItem(equip.getHelmet(), -1) |
| )); |
| |
| tag.putFloatList("HandDropChances", Arrays.asList( |
| equip.getItemInMainHandDropChance(), |
| equip.getItemInOffHandDropChance() |
| )); |
| tag.putFloatList("ArmorDropChances", Arrays.asList( |
| equip.getBootsDropChance(), |
| equip.getLeggingsDropChance(), |
| equip.getChestplateDropChance(), |
| equip.getHelmetDropChance() |
| )); |
| } |
| tag.putBool("CanPickUpLoot", entity.getCanPickupItems()); |
| |
| tag.putBool("Leashed", entity.isLeashed()); |
| |
| if (entity.isLeashed()) { |
| Entity leashHolder = entity.getLeashHolder(); |
| CompoundTag leash = new CompoundTag(); |
| |
| // "Non-living entities excluding leashes will not persist as leash holders." |
| // The empty Leash tag is still persisted tough |
| if (leashHolder instanceof LeashHitch) { |
| Location location = leashHolder.getLocation(); |
| |
| leash.putInt("X", location.getBlockX()); |
| leash.putInt("Y", location.getBlockY()); |
| leash.putInt("Z", location.getBlockZ()); |
| } else if (leashHolder instanceof LivingEntity) { |
| leash.putLong("UUIDMost", entity.getUniqueId().getMostSignificantBits()); |
| leash.putLong("UUIDLeast", entity.getUniqueId().getLeastSignificantBits()); |
| } |
| tag.putCompound("Leash", leash); |
| } |
| } |
| } |