Skip to content
Merged
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 @@ -79,7 +79,9 @@
import net.coreprotect.spigot.SpigotAdapter;
import net.coreprotect.thread.CacheHandler;
import net.coreprotect.thread.Scheduler;
import net.coreprotect.utility.AttributeUtils;
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 @@ -306,11 +308,19 @@ else if (entity instanceof Player) {
}
}

long baselineBits = 0;
if (entity instanceof Attributable) {
Attributable attributable = entity;
for (Attribute attribute : Lists.newArrayList(Registry.ATTRIBUTE)) {
AttributeInstance attributeInstance = attributable.getAttribute(attribute);
if (attributeInstance != null) {
if (attributeInstance == null) {
continue;
}
int baselineBit = AttributeUtils.baselineBit(attributeInstance);
if (baselineBit >= 0) {
baselineBits |= 1L << baselineBit;
}
else {
List<Object> attributeData = new ArrayList<>();
List<Object> attributeModifiers = new ArrayList<>();
attributeData.add(BukkitAdapter.ADAPTER.getRegistryKey(attributeInstance.getAttribute()));
Expand Down Expand Up @@ -582,9 +592,10 @@ 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.BASELINE_ATTRIBUTES);
data.add(baselineBits);

if (!(entity instanceof Player)) {
Queue.queueEntityKill(e, entity.getLocation(), data, type);
Expand Down
124 changes: 124 additions & 0 deletions src/main/java/net/coreprotect/utility/AttributeUtils.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,68 @@
package net.coreprotect.utility;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.attribute.Attributable;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.attribute.AttributeModifier;

public final class AttributeUtils {

/**
* Attribute base values that entity kill data can leave out, frozen from the 26.2 attribute registry defaults.
* Kill data marks each left-out attribute with the bit of its position here, and a restore sets exactly this
* value, whatever the defaults of the restoring server are. Entries may only be appended: changing or reordering
* one would change what the bits of stored kill data mean. The bits are a long, so at most 64 entries fit.
*/
private static final List<BaselineAttribute> BASELINE = List.of(
new BaselineAttribute("air_drag_modifier", 1.0),
new BaselineAttribute("armor", 0.0),
new BaselineAttribute("armor_toughness", 0.0),
new BaselineAttribute("attack_damage", 2.0),
new BaselineAttribute("attack_knockback", 0.0),
new BaselineAttribute("attack_speed", 4.0),
new BaselineAttribute("below_name_distance", 10.0),
new BaselineAttribute("block_break_speed", 1.0),
new BaselineAttribute("block_interaction_range", 4.5),
new BaselineAttribute("bounciness", 0.0),
new BaselineAttribute("burning_time", 1.0),
new BaselineAttribute("camera_distance", 4.0),
new BaselineAttribute("entity_interaction_range", 3.0),
new BaselineAttribute("explosion_knockback_resistance", 0.0),
new BaselineAttribute("fall_damage_multiplier", 1.0),
new BaselineAttribute("flying_speed", 0.4),
new BaselineAttribute("follow_range", 32.0),
new BaselineAttribute("friction_modifier", 1.0),
new BaselineAttribute("gravity", 0.08),
new BaselineAttribute("jump_strength", 0.41999998688697815),
new BaselineAttribute("knockback_resistance", 0.0),
new BaselineAttribute("luck", 0.0),
new BaselineAttribute("max_absorption", 0.0),
new BaselineAttribute("max_health", 20.0),
new BaselineAttribute("mining_efficiency", 0.0),
new BaselineAttribute("movement_efficiency", 0.0),
new BaselineAttribute("movement_speed", 0.7),
new BaselineAttribute("name_tag_distance", 64.0),
new BaselineAttribute("oxygen_bonus", 0.0),
new BaselineAttribute("safe_fall_distance", 3.0),
new BaselineAttribute("scale", 1.0),
new BaselineAttribute("sneaking_speed", 0.3),
new BaselineAttribute("spawn_reinforcements", 0.0),
new BaselineAttribute("step_height", 0.6),
new BaselineAttribute("submerged_mining_speed", 0.2),
new BaselineAttribute("sweeping_damage_ratio", 0.0),
new BaselineAttribute("tempt_range", 10.0),
new BaselineAttribute("water_movement_efficiency", 0.0),
new BaselineAttribute("waypoint_receive_range", 0.0),
new BaselineAttribute("waypoint_transmit_range", 0.0)
);

private AttributeUtils() {
throw new IllegalStateException("Utility class");
}
Expand Down Expand Up @@ -81,4 +135,74 @@ private static boolean isLegacyCategory(String category) {
return category.equals("generic") || category.equals("player")
|| category.equals("horse") || category.equals("zombie");
}

/**
* Returns the baseline bit of an attribute that kill data can leave out: it has no modifiers and its base value is
* the baseline value.
*
* @param attributeInstance
* the attribute of the entity that is being logged
* @return the bit, or -1 when the attribute has to be stored
*/
public static int baselineBit(AttributeInstance attributeInstance) {
if (!attributeInstance.getModifiers().isEmpty()) {
return -1;
}
NamespacedKey key = ((Keyed) attributeInstance.getAttribute()).getKey();
if (!key.getNamespace().equals(NamespacedKey.MINECRAFT)) {
return -1;
}
String name = normalize(key.getKey());
for (int bit = 0; bit < BASELINE.size(); bit++) {
BaselineAttribute baseline = BASELINE.get(bit);
if (baseline.key.equals(name)) {
return Double.compare(attributeInstance.getBaseValue(), baseline.value) == 0 ? bit : -1;
}
}
return -1;
}

/**
* Sets every attribute whose bit is set to its baseline value without modifiers. Attributes without a bit keep the
* value they spawned with, including attributes that did not exist when the kill was logged.
*
* @param attributable
* the restored entity
* @param baselineBits
* the baseline bits stored with the kill data
*/
public static void restoreBaseline(Attributable attributable, long baselineBits) {
for (int bit = 0; bit < BASELINE.size(); bit++) {
if ((baselineBits & (1L << bit)) == 0) {
continue;
}
BaselineAttribute baseline = BASELINE.get(bit);
Attribute attribute;
try {
attribute = resolve(NamespacedKey.MINECRAFT + ":" + baseline.key);
}
catch (IllegalArgumentException exception) {
continue;
}

AttributeInstance attributeInstance = attributable.getAttribute(attribute);
if (attributeInstance == null) {
continue;
}
attributeInstance.setBaseValue(baseline.value);
for (AttributeModifier modifier : new ArrayList<>(attributeInstance.getModifiers())) {
attributeInstance.removeModifier(modifier);
}
}
}

private static final class BaselineAttribute {
private final String key;
private final double value;

private BaselineAttribute(String key, double value) {
this.key = key;
this.value = value;
}
}
}
29 changes: 29 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,19 @@ public class EntityUtil {

private static final long ENTITY_RESTORE_TIMEOUT_SECONDS = 30L;

/**
* Position in the entity kill data list of the attribute format marker, followed by the baseline bits. 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 leaves out the attributes whose bits are set in the long that
* follows the marker (see {@link AttributeUtils#baselineBit}). Rows without the marker list every attribute the
* entity had.
*/
public static final int BASELINE_ATTRIBUTES = 1;

private EntityUtil() {
throw new IllegalStateException("Utility class");
}
Expand Down Expand Up @@ -215,6 +228,10 @@ else if (count == 1) {
Attributable attributable = (Attributable) entity;
@SuppressWarnings("unchecked")
List<Object> attributes = (List<Object>) list.get(5);
Long baselineBits = baselineBits(list);
if (baselineBits != null) {
AttributeUtils.restoreBaseline(attributable, baselineBits);
}
restoreAttributes(attributable, attributes);
}

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

static Long baselineBits(List<Object> list) {
if (list.size() <= ATTRIBUTE_FORMAT_INDEX + 1) {
return null;
}
Object format = list.get(ATTRIBUTE_FORMAT_INDEX);
Object bits = list.get(ATTRIBUTE_FORMAT_INDEX + 1);
if (format instanceof Number && ((Number) format).intValue() == BASELINE_ATTRIBUTES && bits instanceof Long) {
return (Long) bits;
}
return null;
}

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