Remove the concept of non-shared attribute

This commit is contained in:
themode 2021-12-24 10:03:03 +01:00 committed by TheMode
parent eee5778cfe
commit 6aaf5aa0ce
7 changed files with 54 additions and 185 deletions

View File

@ -3,102 +3,34 @@ package net.minestom.server.attribute;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashMap; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* Represents a {@link net.minestom.server.entity.LivingEntity living entity} attribute. * Represents a {@link net.minestom.server.entity.LivingEntity living entity} attribute.
*/ */
public class Attribute { public record Attribute(String key, float defaultValue, float maxValue) {
private static final Map<String, Attribute> ATTRIBUTES = new ConcurrentHashMap<>();
private static final Map<String, Attribute> ATTRIBUTES = new HashMap<>(); public static final Attribute MAX_HEALTH = (new Attribute("generic.max_health", 20, 1024)).register();
public static final Attribute FOLLOW_RANGE = (new Attribute("generic.follow_range", 32, 2048)).register();
public static final Attribute KNOCKBACK_RESISTANCE = (new Attribute("generic.knockback_resistance", 0, 1)).register();
public static final Attribute MOVEMENT_SPEED = (new Attribute("generic.movement_speed", 0.25f, 1024)).register();
public static final Attribute ATTACK_DAMAGE = (new Attribute("generic.attack_damage", 2, 2048)).register();
public static final Attribute ATTACK_SPEED = (new Attribute("generic.attack_speed", 4, 1024)).register();
public static final Attribute FLYING_SPEED = (new Attribute("generic.flying_speed", 0.4f, 1024)).register();
public static final Attribute ARMOR = (new Attribute("generic.armor", 0, 30)).register();
public static final Attribute ARMOR_TOUGHNESS = (new Attribute("generic.armor_toughness", 0, 20)).register();
public static final Attribute ATTACK_KNOCKBACK = (new Attribute("generic.attack_knockback", 0, 5)).register();
public static final Attribute LUCK = (new Attribute("generic.luck", 0, 1024)).register();
public static final Attribute HORSE_JUMP_STRENGTH = (new Attribute("horse.jump_strength", 0.7f, 2)).register();
public static final Attribute ZOMBIE_SPAWN_REINFORCEMENTS = (new Attribute("zombie.spawn_reinforcements", 0, 1)).register();
public static final Attribute MAX_HEALTH = (new Attribute("generic.max_health", true, 20, 1024)).register(); public Attribute {
public static final Attribute FOLLOW_RANGE = (new Attribute("generic.follow_range", true, 32, 2048)).register();
public static final Attribute KNOCKBACK_RESISTANCE = (new Attribute("generic.knockback_resistance", true, 0, 1)).register();
public static final Attribute MOVEMENT_SPEED = (new Attribute("generic.movement_speed", true, 0.25f, 1024)).register();
public static final Attribute ATTACK_DAMAGE = (new Attribute("generic.attack_damage", true, 2, 2048)).register();
public static final Attribute ATTACK_SPEED = (new Attribute("generic.attack_speed", true, 4, 1024)).register();
public static final Attribute FLYING_SPEED = (new Attribute("generic.flying_speed", true, 0.4f, 1024)).register();
public static final Attribute ARMOR = (new Attribute("generic.armor", true, 0, 30)).register();
public static final Attribute ARMOR_TOUGHNESS = (new Attribute("generic.armor_toughness", true, 0, 20)).register();
public static final Attribute ATTACK_KNOCKBACK = (new Attribute("generic.attack_knockback", true, 0, 5)).register();
public static final Attribute LUCK = (new Attribute("generic.luck", true, 0, 1024)).register();
public static final Attribute HORSE_JUMP_STRENGTH = (new Attribute("horse.jump_strength", true, 0.7f, 2)).register();
public static final Attribute ZOMBIE_SPAWN_REINFORCEMENTS = (new Attribute("zombie.spawn_reinforcements", true, 0, 1)).register();
private final String key;
private final float defaultValue;
private final float maxValue;
private final boolean shareWithClient;
/**
* Create a new attribute with a given key and default.
* <p>
* By default, this attribute will be sent to the client.
* </p>
*
* @param key the attribute registry key
* @param defaultValue the default value
* @param maxValue the maximum allowed value
*/
public Attribute(@NotNull String key, float defaultValue, float maxValue) {
this(key, true, defaultValue, maxValue);
}
/**
* Create a new attribute with a given key and default.
*
* @param key the attribute registry key
* @param shareWithClient whether to send this attribute to the client
* @param defaultValue the default value
* @param maxValue the maximum allowed value
*/
public Attribute(@NotNull String key, boolean shareWithClient, float defaultValue, float maxValue) {
if (defaultValue > maxValue) { if (defaultValue > maxValue) {
throw new IllegalArgumentException("Default value cannot be greater than the maximum allowed"); throw new IllegalArgumentException("Default value cannot be greater than the maximum allowed");
} }
this.key = key;
this.shareWithClient = shareWithClient;
this.defaultValue = defaultValue;
this.maxValue = maxValue;
}
/**
* Gets the attribute unique key.
*
* @return the attribute key
*/
@NotNull
public String getKey() {
return key;
}
/**
* Gets the attribute default value that should be applied.
*
* @return the attribute default value
*/
public float getDefaultValue() {
return defaultValue;
}
/**
* Gets the maximum value applicable to an entity for this attribute.
*
* @return the maximum value of this attribute
*/
public float getMaxValue() {
return maxValue;
}
/**
* Gets whether this attribute's instances should be sent to clients.
*
* @return if this attribute is to be shared
*/
public boolean isShared() {
return shareWithClient;
} }
/** /**
@ -108,8 +40,7 @@ public class Attribute {
* @see #fromKey(String) * @see #fromKey(String)
* @see #values() * @see #values()
*/ */
@NotNull public @NotNull Attribute register() {
public Attribute register() {
ATTRIBUTES.put(key, this); ATTRIBUTES.put(key, this);
return this; return this;
} }
@ -120,8 +51,7 @@ public class Attribute {
* @param key the key of the attribute * @param key the key of the attribute
* @return the attribute for the key or null if not any * @return the attribute for the key or null if not any
*/ */
@Nullable public static @Nullable Attribute fromKey(@NotNull String key) {
public static Attribute fromKey(@NotNull String key) {
return ATTRIBUTES.get(key); return ATTRIBUTES.get(key);
} }
@ -130,18 +60,7 @@ public class Attribute {
* *
* @return an array containing all registered attributes * @return an array containing all registered attributes
*/ */
@NotNull public static @NotNull Collection<@NotNull Attribute> values() {
public static Attribute[] values() { return ATTRIBUTES.values();
return ATTRIBUTES.values().toArray(new Attribute[0]);
}
/**
* Retrieves registered attributes that are shared with the client.
*
* @return an array containing registered, sharable attributes
*/
@NotNull
public static Attribute[] sharedAttributes() {
return ATTRIBUTES.values().stream().filter(Attribute::isShared).toArray(Attribute[]::new);
} }
} }

View File

@ -12,8 +12,7 @@ import java.util.function.Consumer;
/** /**
* Represents an instance of an attribute and its modifiers. * Represents an instance of an attribute and its modifiers.
*/ */
public class AttributeInstance { public final class AttributeInstance {
private final Attribute attribute; private final Attribute attribute;
private final Map<UUID, AttributeModifier> modifiers = new HashMap<>(); private final Map<UUID, AttributeModifier> modifiers = new HashMap<>();
private final Consumer<AttributeInstance> propertyChangeListener; private final Consumer<AttributeInstance> propertyChangeListener;
@ -23,7 +22,7 @@ public class AttributeInstance {
public AttributeInstance(@NotNull Attribute attribute, @Nullable Consumer<AttributeInstance> listener) { public AttributeInstance(@NotNull Attribute attribute, @Nullable Consumer<AttributeInstance> listener) {
this.attribute = attribute; this.attribute = attribute;
this.propertyChangeListener = listener; this.propertyChangeListener = listener;
this.baseValue = attribute.getDefaultValue(); this.baseValue = attribute.defaultValue();
refreshCachedValue(); refreshCachedValue();
} }
@ -32,8 +31,7 @@ public class AttributeInstance {
* *
* @return the associated attribute * @return the associated attribute
*/ */
@NotNull public @NotNull Attribute getAttribute() {
public Attribute getAttribute() {
return attribute; return attribute;
} }
@ -104,7 +102,7 @@ public class AttributeInstance {
/** /**
* Recalculate the value of this attribute instance using the modifiers. * Recalculate the value of this attribute instance using the modifiers.
*/ */
protected void refreshCachedValue() { private void refreshCachedValue() {
final Collection<AttributeModifier> modifiers = getModifiers(); final Collection<AttributeModifier> modifiers = getModifiers();
float base = getBaseValue(); float base = getBaseValue();
@ -121,7 +119,7 @@ public class AttributeInstance {
result *= (1.0f + modifier.getAmount()); result *= (1.0f + modifier.getAmount());
} }
this.cachedValue = Math.min(result, getAttribute().getMaxValue()); this.cachedValue = Math.min(result, getAttribute().maxValue());
// Signal entity // Signal entity
if (propertyChangeListener != null) { if (propertyChangeListener != null) {

View File

@ -18,8 +18,7 @@ public enum AttributeOperation {
return this.id; return this.id;
} }
@Nullable public static @Nullable AttributeOperation fromId(int id) {
public static AttributeOperation fromId(int id) {
if (id >= 0 && id < VALUES.length) { if (id >= 0 && id < VALUES.length) {
return VALUES[id]; return VALUES[id];
} }

View File

@ -1,32 +0,0 @@
package net.minestom.server.attribute;
/**
* The Minecraft, vanilla, standards attributes.
*
* @deprecated use the constants in {@link Attribute}
*/
@Deprecated
public final class Attributes {
public static final Attribute MAX_HEALTH = Attribute.MAX_HEALTH;
public static final Attribute FOLLOW_RANGE = Attribute.FOLLOW_RANGE;
public static final Attribute KNOCKBACK_RESISTANCE = Attribute.KNOCKBACK_RESISTANCE;
public static final Attribute MOVEMENT_SPEED = Attribute.MOVEMENT_SPEED;
public static final Attribute ATTACK_DAMAGE = Attribute.ATTACK_DAMAGE;
public static final Attribute ATTACK_SPEED = Attribute.ATTACK_SPEED;
public static final Attribute FLYING_SPEED = Attribute.FLYING_SPEED;
public static final Attribute ARMOR = Attribute.ARMOR;
public static final Attribute ARMOR_TOUGHNESS = Attribute.ARMOR_TOUGHNESS;
public static final Attribute ATTACK_KNOCKBACK = Attribute.ATTACK_KNOCKBACK;
public static final Attribute LUCK = Attribute.LUCK;
public static final Attribute HORSE_JUMP_STRENGTH = Attribute.HORSE_JUMP_STRENGTH;
public static final Attribute ZOMBIE_SPAWN_REINFORCEMENTS = Attribute.ZOMBIE_SPAWN_REINFORCEMENTS;
private Attributes() throws IllegalAccessException {
throw new IllegalAccessException("Cannot instantiate a static class");
}
protected static void init() {
// Empty, here to register all the vanilla attributes
}
}

View File

@ -3,7 +3,6 @@ package net.minestom.server.entity;
import net.kyori.adventure.sound.Sound.Source; import net.kyori.adventure.sound.Sound.Source;
import net.minestom.server.attribute.Attribute; import net.minestom.server.attribute.Attribute;
import net.minestom.server.attribute.AttributeInstance; import net.minestom.server.attribute.AttributeInstance;
import net.minestom.server.attribute.Attributes;
import net.minestom.server.collision.BoundingBox; import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
@ -35,7 +34,10 @@ import org.jetbrains.annotations.Nullable;
import java.time.Duration; import java.time.Duration;
import java.time.temporal.TemporalUnit; import java.time.temporal.TemporalUnit;
import java.util.*; import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class LivingEntity extends Entity implements EquipmentHandler { public class LivingEntity extends Entity implements EquipmentHandler {
@ -51,7 +53,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
// Bounding box used for items' pickup (see LivingEntity#setBoundingBox) // Bounding box used for items' pickup (see LivingEntity#setBoundingBox)
protected BoundingBox expandedBoundingBox; protected BoundingBox expandedBoundingBox;
private final Map<String, AttributeInstance> attributeModifiers = new ConcurrentHashMap<>(Attribute.values().length); private final Map<String, AttributeInstance> attributeModifiers = new ConcurrentHashMap<>();
// Abilities // Abilities
protected boolean invulnerable; protected boolean invulnerable;
@ -423,7 +425,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
} }
/** /**
* Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attributes#MAX_HEALTH}. * Gets the entity max health from {@link #getAttributeValue(Attribute)} {@link Attribute#MAX_HEALTH}.
* *
* @return the entity max health * @return the entity max health
*/ */
@ -434,7 +436,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
/** /**
* Sets the heal of the entity as its max health. * Sets the heal of the entity as its max health.
* <p> * <p>
* Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attributes#MAX_HEALTH}. * Retrieved from {@link #getAttributeValue(Attribute)} with the attribute {@link Attribute#MAX_HEALTH}.
*/ */
public void heal() { public void heal() {
setHealth(getAttributeValue(Attribute.MAX_HEALTH)); setHealth(getAttributeValue(Attribute.MAX_HEALTH));
@ -446,9 +448,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* @param attribute the attribute instance to get * @param attribute the attribute instance to get
* @return the attribute instance * @return the attribute instance
*/ */
@NotNull public @NotNull AttributeInstance getAttribute(@NotNull Attribute attribute) {
public AttributeInstance getAttribute(@NotNull Attribute attribute) { return attributeModifiers.computeIfAbsent(attribute.key(),
return attributeModifiers.computeIfAbsent(attribute.getKey(),
s -> new AttributeInstance(attribute, this::onAttributeChanged)); s -> new AttributeInstance(attribute, this::onAttributeChanged));
} }
@ -458,21 +459,19 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* @param attributeInstance the modified attribute instance * @param attributeInstance the modified attribute instance
*/ */
protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) { protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) {
if (attributeInstance.getAttribute().isShared()) {
boolean self = false; boolean self = false;
if (this instanceof Player player) { if (this instanceof Player player) {
PlayerConnection playerConnection = player.playerConnection; PlayerConnection playerConnection = player.playerConnection;
// connection null during Player initialization (due to #super call) // connection null during Player initialization (due to #super call)
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY; self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
} }
EntityPropertiesPacket propertiesPacket = getPropertiesPacket(Collections.singleton(attributeInstance)); EntityPropertiesPacket propertiesPacket = new EntityPropertiesPacket(getEntityId(), List.of(attributeInstance));
if (self) { if (self) {
sendPacketToViewersAndSelf(propertiesPacket); sendPacketToViewersAndSelf(propertiesPacket);
} else { } else {
sendPacketToViewers(propertiesPacket); sendPacketToViewers(propertiesPacket);
} }
} }
}
/** /**
* Retrieves the attribute value. * Retrieves the attribute value.
@ -481,8 +480,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* @return the attribute value * @return the attribute value
*/ */
public float getAttributeValue(@NotNull Attribute attribute) { public float getAttributeValue(@NotNull Attribute attribute) {
AttributeInstance instance = attributeModifiers.get(attribute.getKey()); AttributeInstance instance = attributeModifiers.get(attribute.key());
return (instance != null) ? instance.getValue() : attribute.getDefaultValue(); return (instance != null) ? instance.getValue() : attribute.defaultValue();
} }
/** /**
@ -575,22 +574,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* *
* @return an {@link EntityPropertiesPacket} linked to this entity * @return an {@link EntityPropertiesPacket} linked to this entity
*/ */
@NotNull protected @NotNull EntityPropertiesPacket getPropertiesPacket() {
protected EntityPropertiesPacket getPropertiesPacket() { return new EntityPropertiesPacket(getEntityId(), List.copyOf(attributeModifiers.values()));
return getPropertiesPacket(attributeModifiers.values());
}
/**
* Gets an {@link EntityPropertiesPacket} for this entity with the specified attribute values.
*
* @param attributes the attributes to include in the packet
* @return an {@link EntityPropertiesPacket} linked to this entity
*/
protected @NotNull EntityPropertiesPacket getPropertiesPacket(@NotNull Collection<AttributeInstance> attributes) {
// Get all the attributes which should be sent to the client
final List<AttributeInstance> properties = attributes.stream()
.filter(i -> i.getAttribute().isShared()).toList();
return new EntityPropertiesPacket(getEntityId(), properties);
} }
@Override @Override

View File

@ -128,7 +128,7 @@ public abstract class ItemMetaBuilder implements TagWritable {
"UUID", NBT.IntArray(Utils.uuidToIntArray(itemAttribute.getUuid())), "UUID", NBT.IntArray(Utils.uuidToIntArray(itemAttribute.getUuid())),
"Amount", NBT.Double(itemAttribute.getValue()), "Amount", NBT.Double(itemAttribute.getValue()),
"Slot", NBT.String(itemAttribute.getSlot().name().toLowerCase()), "Slot", NBT.String(itemAttribute.getSlot().name().toLowerCase()),
"AttributeName", NBT.String(itemAttribute.getAttribute().getKey()), "AttributeName", NBT.String(itemAttribute.getAttribute().key()),
"Operation", NBT.Int(itemAttribute.getOperation().getId()), "Operation", NBT.Int(itemAttribute.getOperation().getId()),
"Name", NBT.String(itemAttribute.getInternalName())))) "Name", NBT.String(itemAttribute.getInternalName()))))
.toList() .toList()

View File

@ -39,7 +39,7 @@ public record EntityPropertiesPacket(int entityId, List<AttributeInstance> prope
for (AttributeInstance instance : properties) { for (AttributeInstance instance : properties) {
final Attribute attribute = instance.getAttribute(); final Attribute attribute = instance.getAttribute();
writer.writeSizedString(attribute.getKey()); writer.writeSizedString(attribute.key());
writer.writeDouble(instance.getBaseValue()); writer.writeDouble(instance.getBaseValue());
{ {