Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import net.coreprotect.thread.CacheHandler;
import net.coreprotect.thread.Scheduler;
import net.coreprotect.utility.EntitySpawnTracking;
import net.coreprotect.utility.entity.EntityUtil;
import net.coreprotect.utility.entity.LivingEntityDetails;
import net.coreprotect.utility.serialize.ItemMetaHandler;

Expand Down Expand Up @@ -310,7 +311,7 @@ else if (entity instanceof Player) {
Attributable attributable = entity;
for (Attribute attribute : Lists.newArrayList(Registry.ATTRIBUTE)) {
AttributeInstance attributeInstance = attributable.getAttribute(attribute);
if (attributeInstance != null) {
if (attributeInstance != null && EntityUtil.isAttributeModified(attributeInstance)) {
List<Object> attributeData = new ArrayList<>();
List<Object> attributeModifiers = new ArrayList<>();
attributeData.add(BukkitAdapter.ADAPTER.getRegistryKey(attributeInstance.getAttribute()));
Expand Down Expand Up @@ -582,9 +583,9 @@ else if (entity instanceof Zoglin) {
data.add(entity.getCustomName());
data.add(attributes);
data.add(details);
if (EntitySpawnTracking.isTracked(entity)) {
data.add(entity.getUniqueId().toString());
}
data.add(EntitySpawnTracking.isTracked(entity) ? entity.getUniqueId().toString() : null);
data.add(null); // kill location, only stored for placed entities
data.add(EntityUtil.SPARSE_ATTRIBUTES);

if (!(entity instanceof Player)) {
Queue.queueEntityKill(e, entity.getLocation(), data, type);
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/net/coreprotect/utility/entity/EntityUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ public class EntityUtil {

private static final long ENTITY_RESTORE_TIMEOUT_SECONDS = 30L;

/**
* Position in the entity kill data list of the attribute format marker. Index 7 holds the entity UUID or null,
* and index 8 the kill location that EntitySpawnTracking stores for placed entities.
*/
public static final int ATTRIBUTE_FORMAT_INDEX = 9;

/**
* Attribute format marker: the attribute list omits every attribute that had the default base value and no
* modifiers. Rows without the marker list every attribute the entity had.
*/
public static final int SPARSE_ATTRIBUTES = 1;

private EntityUtil() {
throw new IllegalStateException("Utility class");
}
Expand Down Expand Up @@ -215,6 +227,9 @@ else if (count == 1) {
Attributable attributable = (Attributable) entity;
@SuppressWarnings("unchecked")
List<Object> attributes = (List<Object>) list.get(5);
if (hasSparseAttributes(list)) {
resetAttributes(attributeInstances(attributable));
}
restoreAttributes(attributable, attributes);
}

Expand Down Expand Up @@ -717,6 +732,52 @@ else if (entity instanceof Zoglin) {
return completion;
}

/**
* Returns whether an attribute has to be stored to restore the entity: its base value differs from the default,
* or it has modifiers. {@link #resetAttributes(Iterable)} rebuilds every attribute this skips.
*
* @param attributeInstance
* the attribute of the entity that is being logged
* @return true when the attribute must be stored
*/
@SuppressWarnings("deprecation") // The server default exists on every supported version; restore uses the same value.
public static boolean isAttributeModified(AttributeInstance attributeInstance) {
return !attributeInstance.getModifiers().isEmpty() || Double.compare(attributeInstance.getBaseValue(), attributeInstance.getDefaultValue()) != 0;
}

static boolean hasSparseAttributes(List<Object> list) {
if (list.size() <= ATTRIBUTE_FORMAT_INDEX) {
return false;
}
Object format = list.get(ATTRIBUTE_FORMAT_INDEX);
return format instanceof Number && ((Number) format).intValue() == SPARSE_ATTRIBUTES;
}

private static List<AttributeInstance> attributeInstances(Attributable attributable) {
List<AttributeInstance> attributeInstances = new ArrayList<>();
for (Attribute attribute : Registry.ATTRIBUTE) {
AttributeInstance attributeInstance = attributable.getAttribute(attribute);
if (attributeInstance != null) {
attributeInstances.add(attributeInstance);
}
}
return attributeInstances;
}

/**
* Sets attributes of a restored entity to their default base value without modifiers, so that attributes a
* sparse attribute list omits match the logged entity. Spawning can randomize base values and add modifiers.
*/
@SuppressWarnings("deprecation") // Same default value that isAttributeModified compares against.
static void resetAttributes(Iterable<AttributeInstance> attributeInstances) {
for (AttributeInstance attributeInstance : attributeInstances) {
attributeInstance.setBaseValue(attributeInstance.getDefaultValue());
for (AttributeModifier modifier : new ArrayList<>(attributeInstance.getModifiers())) {
attributeInstance.removeModifier(modifier);
}
}
}

static void restoreAttributes(Attributable attributable, List<Object> attributes) {
for (Object value : attributes) {
@SuppressWarnings("unchecked")
Expand Down