blob: 02d8bf72776bc621ef5b73b4795203e8973824cf [file] [log] [blame] [raw]
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);
}
}
}