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.Nullable;
import java.util.HashMap;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 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 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) {
public Attribute {
if (defaultValue > maxValue) {
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 #values()
*/
@NotNull
public Attribute register() {
public @NotNull Attribute register() {
ATTRIBUTES.put(key, this);
return this;
}
@ -120,8 +51,7 @@ public class Attribute {
* @param key the key of the attribute
* @return the attribute for the key or null if not any
*/
@Nullable
public static Attribute fromKey(@NotNull String key) {
public static @Nullable Attribute fromKey(@NotNull String key) {
return ATTRIBUTES.get(key);
}
@ -130,18 +60,7 @@ public class Attribute {
*
* @return an array containing all registered attributes
*/
@NotNull
public static Attribute[] 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);
public static @NotNull Collection<@NotNull Attribute> values() {
return ATTRIBUTES.values();
}
}

View File

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

View File

@ -18,8 +18,7 @@ public enum AttributeOperation {
return this.id;
}
@Nullable
public static AttributeOperation fromId(int id) {
public static @Nullable AttributeOperation fromId(int id) {
if (id >= 0 && id < VALUES.length) {
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.minestom.server.attribute.Attribute;
import net.minestom.server.attribute.AttributeInstance;
import net.minestom.server.attribute.Attributes;
import net.minestom.server.collision.BoundingBox;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
@ -35,7 +34,10 @@ import org.jetbrains.annotations.Nullable;
import java.time.Duration;
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;
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)
protected BoundingBox expandedBoundingBox;
private final Map<String, AttributeInstance> attributeModifiers = new ConcurrentHashMap<>(Attribute.values().length);
private final Map<String, AttributeInstance> attributeModifiers = new ConcurrentHashMap<>();
// Abilities
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
*/
@ -434,7 +436,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
/**
* Sets the heal of the entity as its max health.
* <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() {
setHealth(getAttributeValue(Attribute.MAX_HEALTH));
@ -446,9 +448,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* @param attribute the attribute instance to get
* @return the attribute instance
*/
@NotNull
public AttributeInstance getAttribute(@NotNull Attribute attribute) {
return attributeModifiers.computeIfAbsent(attribute.getKey(),
public @NotNull AttributeInstance getAttribute(@NotNull Attribute attribute) {
return attributeModifiers.computeIfAbsent(attribute.key(),
s -> new AttributeInstance(attribute, this::onAttributeChanged));
}
@ -458,19 +459,17 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* @param attributeInstance the modified attribute instance
*/
protected void onAttributeChanged(@NotNull AttributeInstance attributeInstance) {
if (attributeInstance.getAttribute().isShared()) {
boolean self = false;
if (this instanceof Player player) {
PlayerConnection playerConnection = player.playerConnection;
// connection null during Player initialization (due to #super call)
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
}
EntityPropertiesPacket propertiesPacket = getPropertiesPacket(Collections.singleton(attributeInstance));
if (self) {
sendPacketToViewersAndSelf(propertiesPacket);
} else {
sendPacketToViewers(propertiesPacket);
}
boolean self = false;
if (this instanceof Player player) {
PlayerConnection playerConnection = player.playerConnection;
// connection null during Player initialization (due to #super call)
self = playerConnection != null && playerConnection.getConnectionState() == ConnectionState.PLAY;
}
EntityPropertiesPacket propertiesPacket = new EntityPropertiesPacket(getEntityId(), List.of(attributeInstance));
if (self) {
sendPacketToViewersAndSelf(propertiesPacket);
} else {
sendPacketToViewers(propertiesPacket);
}
}
@ -481,8 +480,8 @@ public class LivingEntity extends Entity implements EquipmentHandler {
* @return the attribute value
*/
public float getAttributeValue(@NotNull Attribute attribute) {
AttributeInstance instance = attributeModifiers.get(attribute.getKey());
return (instance != null) ? instance.getValue() : attribute.getDefaultValue();
AttributeInstance instance = attributeModifiers.get(attribute.key());
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
*/
@NotNull
protected EntityPropertiesPacket getPropertiesPacket() {
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);
protected @NotNull EntityPropertiesPacket getPropertiesPacket() {
return new EntityPropertiesPacket(getEntityId(), List.copyOf(attributeModifiers.values()));
}
@Override

View File

@ -128,7 +128,7 @@ public abstract class ItemMetaBuilder implements TagWritable {
"UUID", NBT.IntArray(Utils.uuidToIntArray(itemAttribute.getUuid())),
"Amount", NBT.Double(itemAttribute.getValue()),
"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()),
"Name", NBT.String(itemAttribute.getInternalName()))))
.toList()

View File

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