From 5818060656bde9ee5ab0bd1278dace0b400615bc Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 24 Feb 2025 16:08:22 -0500 Subject: [PATCH] Large Unused Attribute Push --- .../npcs/attribute/AttributeController.java | 32 +++++ .../npcs/attribute/AttributeDefinition.java | 36 ++++++ .../npcs/attribute/AttributeModifier.java | 38 ++++++ .../attribute/CNPCItemAttributeHelper.java | 79 ++++++++++++ .../npcs/attribute/CustomAttributeMap.java | 29 +++++ .../npcs/attribute/DamageCalculator.java | 59 +++++++++ .../npcs/attribute/IAttributeInstance.java | 17 +++ .../noppes/npcs/attribute/ModAttributes.java | 21 ++++ .../ModifiableAttributeInstance.java | 66 ++++++++++ .../java/noppes/npcs/attribute/Operation.java | 6 + .../attribute/PlayerAttributeManager.java | 31 +++++ .../attribute/PlayerAttributeTickHandler.java | 21 ++++ .../attribute/PlayerAttributeTracker.java | 118 ++++++++++++++++++ 13 files changed, 553 insertions(+) create mode 100644 src/main/java/noppes/npcs/attribute/AttributeController.java create mode 100644 src/main/java/noppes/npcs/attribute/AttributeDefinition.java create mode 100644 src/main/java/noppes/npcs/attribute/AttributeModifier.java create mode 100644 src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java create mode 100644 src/main/java/noppes/npcs/attribute/CustomAttributeMap.java create mode 100644 src/main/java/noppes/npcs/attribute/DamageCalculator.java create mode 100644 src/main/java/noppes/npcs/attribute/IAttributeInstance.java create mode 100644 src/main/java/noppes/npcs/attribute/ModAttributes.java create mode 100644 src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java create mode 100644 src/main/java/noppes/npcs/attribute/Operation.java create mode 100644 src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java create mode 100644 src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java create mode 100644 src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java diff --git a/src/main/java/noppes/npcs/attribute/AttributeController.java b/src/main/java/noppes/npcs/attribute/AttributeController.java new file mode 100644 index 000000000..4e4ffbb5d --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/AttributeController.java @@ -0,0 +1,32 @@ +package noppes.npcs.attribute; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Central registry for valid attribute definitions. + * Other mods can register new attributes here so that items only need to store a + * small namespaced key. + */ +public class AttributeController { + private static final Map definitions = new HashMap<>(); + private static int nextId = 0; + + public static AttributeDefinition registerAttribute(String key, String displayName, Operation op) { + if (definitions.containsKey(key)) { + return definitions.get(key); + } + AttributeDefinition def = new AttributeDefinition(nextId++, key, displayName, op); + definitions.put(key, def); + return def; + } + + public static AttributeDefinition getAttribute(String key) { + return definitions.get(key); + } + + public static Collection getAllAttributes() { + return definitions.values(); + } +} diff --git a/src/main/java/noppes/npcs/attribute/AttributeDefinition.java b/src/main/java/noppes/npcs/attribute/AttributeDefinition.java new file mode 100644 index 000000000..5ba597661 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/AttributeDefinition.java @@ -0,0 +1,36 @@ +package noppes.npcs.attribute; + +/** + * Defines a valid attribute – for example, "cnpc:main_attack_flat" (a flat bonus) + * or "cnpc:main_attack_percent" (a percent bonus). These definitions are registered + * once in the AttributeController. + */ +public class AttributeDefinition { + private final int id; + private final String key; // e.g., "cnpc:main_attack_flat" + private final String displayName; // Human‑readable name + private final Operation operation; + + public AttributeDefinition(int id, String key, String displayName, Operation operation) { + this.id = id; + this.key = key; + this.displayName = displayName; + this.operation = operation; + } + + public int getId() { + return id; + } + + public String getKey() { + return key; + } + + public String getDisplayName() { + return displayName; + } + + public Operation getOperation() { + return operation; + } +} diff --git a/src/main/java/noppes/npcs/attribute/AttributeModifier.java b/src/main/java/noppes/npcs/attribute/AttributeModifier.java new file mode 100644 index 000000000..9f821283f --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/AttributeModifier.java @@ -0,0 +1,38 @@ +package noppes.npcs.attribute; + +import java.util.UUID; + +/** + * An AttributeModifier represents a bonus or penalty that’s applied to an attribute. + * It stores a unique UUID, a name, an amount, and whether it is FLAT (added directly) + * or PERCENT (applied as a multiplier) to the base value. + */ +public class AttributeModifier { + private final UUID id; + private final String name; + private final double amount; + private final Operation operation; + + public AttributeModifier(UUID id, String name, double amount, Operation operation) { + this.id = id; + this.name = name; + this.amount = amount; + this.operation = operation; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public double getAmount() { + return amount; + } + + public Operation getOperation() { + return operation; + } +} diff --git a/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java b/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java new file mode 100644 index 000000000..44b286773 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java @@ -0,0 +1,79 @@ +package noppes.npcs.attribute; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import java.util.Set; +import java.util.HashMap; +import java.util.Map; + +/** + * Handles writing/reading item attributes. + * Non‑magic attributes are stored under a "CNPCAttributes" tag. + * Magic-based attributes are stored as a compound mapping magic ID to value, + * for example under "cnpc:magic_damage_flat". + */ +public class CNPCItemAttributeHelper { + public static final String TAG_ROOT = "CNPCAttributes"; + + public static void applyAttribute(ItemStack item, String attributeKey, double value) { + if(item == null) return; + if(item.stackTagCompound == null) { + item.stackTagCompound = new NBTTagCompound(); + } + NBTTagCompound root = item.stackTagCompound; + NBTTagCompound attrTag; + if(root.hasKey(TAG_ROOT)) { + attrTag = root.getCompoundTag(TAG_ROOT); + } else { + attrTag = new NBTTagCompound(); + } + attrTag.setDouble(attributeKey, value); + root.setTag(TAG_ROOT, attrTag); + } + + public static Map readAttributes(ItemStack item) { + Map map = new HashMap<>(); + if(item == null || item.stackTagCompound == null) return map; + NBTTagCompound root = item.stackTagCompound; + if(root.hasKey(TAG_ROOT)) { + NBTTagCompound attrTag = root.getCompoundTag(TAG_ROOT); + Set keys = attrTag.func_150296_c(); + for(String key : keys) { + map.put(key, attrTag.getDouble(key)); + } + } + return map; + } + + public static Map readMagicAttributeMap(ItemStack item, String attributeTag) { + Map map = new HashMap<>(); + if(item == null || item.stackTagCompound == null) return map; + if(item.stackTagCompound.hasKey(attributeTag)) { + NBTTagCompound magicMap = item.stackTagCompound.getCompoundTag(attributeTag); + Set keys = magicMap.func_150296_c(); + for(String key : keys) { + try { + int magicId = Integer.parseInt(key); + map.put(magicId, magicMap.getDouble(key)); + } catch(NumberFormatException e) { + // Skip invalid key. + } + } + } + return map; + } + + public static void writeMagicAttribute(ItemStack item, String attributeTag, int magicId, double value) { + if(item == null) return; + if(item.stackTagCompound == null) + item.stackTagCompound = new NBTTagCompound(); + NBTTagCompound magicMap; + if(item.stackTagCompound.hasKey(attributeTag)) { + magicMap = item.stackTagCompound.getCompoundTag(attributeTag); + } else { + magicMap = new NBTTagCompound(); + } + magicMap.setDouble(String.valueOf(magicId), value); + item.stackTagCompound.setTag(attributeTag, magicMap); + } +} diff --git a/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java b/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java new file mode 100644 index 000000000..bc8d8780a --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java @@ -0,0 +1,29 @@ +package noppes.npcs.attribute; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Holds a collection of attribute instances (for a weapon, item, or aggregated for a player). + */ +public class CustomAttributeMap { + private final Map map = new HashMap<>(); + + public IAttributeInstance registerAttribute(AttributeDefinition attribute, double baseValue) { + if (map.containsKey(attribute)) { + throw new IllegalArgumentException("Attribute already registered: " + attribute.getKey()); + } + IAttributeInstance instance = new ModifiableAttributeInstance(attribute, baseValue); + map.put(attribute, instance); + return instance; + } + + public IAttributeInstance getAttributeInstance(AttributeDefinition attribute) { + return map.get(attribute); + } + + public Collection getAllAttributes() { + return map.values(); + } +} diff --git a/src/main/java/noppes/npcs/attribute/DamageCalculator.java b/src/main/java/noppes/npcs/attribute/DamageCalculator.java new file mode 100644 index 000000000..c8258b20c --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/DamageCalculator.java @@ -0,0 +1,59 @@ +package noppes.npcs.attribute; + +import java.util.Map; +import java.util.Random; +import java.util.Collection; +import net.minecraft.item.ItemStack; + +/** + * Calculates a weapon’s total damage against an armor set. + * It reads the weapon’s main attack (flat and percent) plus any magic-based attributes. + * Then applies a critical chance (if the random roll is below the crit chance, + * the damage is doubled and a flat crit bonus is added). + */ +public class DamageCalculator { + private static final Random random = new Random(); + + public static double calculateTotalDamage(ItemStack weapon, Collection armorSet) { + // Read non-magic weapon attributes: + Map weaponAttrs = CNPCItemAttributeHelper.readAttributes(weapon); + double mainFlat = weaponAttrs.getOrDefault("cnpc:main_attack_flat", 0.0); + double mainPercent = weaponAttrs.getOrDefault("cnpc:main_attack_percent", 0.0); + double baseDamage = mainFlat * (1 + mainPercent); + + double totalMagicDamage = 0.0; + Map weaponMagicFlat = CNPCItemAttributeHelper.readMagicAttributeMap(weapon, "cnpc:magic_damage_flat"); + Map weaponMagicPercent = CNPCItemAttributeHelper.readMagicAttributeMap(weapon, "cnpc:magic_damage_percent"); + + for(Map.Entry entry : weaponMagicFlat.entrySet()) { + int magicId = entry.getKey(); + double flat = entry.getValue(); + double percent = weaponMagicPercent.getOrDefault(magicId, 0.0); + double weaponMagicDamage = flat * (1 + percent); + + // Aggregate defenses from all armor pieces for this magic. + double armorMagicDefense = 0.0; + double armorMagicResistance = 0.0; + for(ItemStack armor : armorSet) { + Map armorDefFlat = CNPCItemAttributeHelper.readMagicAttributeMap(armor, "cnpc:magic_defense_flat"); + Map armorResistPercent = CNPCItemAttributeHelper.readMagicAttributeMap(armor, "cnpc:magic_resistance_percent"); + armorMagicDefense += armorDefFlat.getOrDefault(magicId, 0.0); + armorMagicResistance += armorResistPercent.getOrDefault(magicId, 0.0); + } + double effectiveMagicDamage = weaponMagicDamage - armorMagicDefense; + effectiveMagicDamage *= (1 - armorMagicResistance); + if(effectiveMagicDamage < 0) effectiveMagicDamage = 0; + totalMagicDamage += effectiveMagicDamage; + } + + double totalDamage = baseDamage + totalMagicDamage; + + // Process critical hit chance. + double critChance = weaponAttrs.getOrDefault("cnpc:critical_chance_percent", 0.0); + double critBonus = weaponAttrs.getOrDefault("cnpc:critical_damage_flat", 0.0); + if(random.nextDouble() < critChance) { + totalDamage = totalDamage * 2 + critBonus; + } + return Math.max(totalDamage, 0); + } +} diff --git a/src/main/java/noppes/npcs/attribute/IAttributeInstance.java b/src/main/java/noppes/npcs/attribute/IAttributeInstance.java new file mode 100644 index 000000000..67bab94b5 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/IAttributeInstance.java @@ -0,0 +1,17 @@ +package noppes.npcs.attribute; + +import java.util.Collection; + +/** + * Represents an attribute instance – a value (plus any applied modifiers) + * corresponding to a given AttributeDefinition. + */ +public interface IAttributeInstance { + AttributeDefinition getAttribute(); + double getBaseValue(); + void setBaseValue(double value); + void applyModifier(AttributeModifier modifier); + void removeModifier(AttributeModifier modifier); + Collection getModifiers(); + double getAttributeValue(); // Final value after modifiers. +} diff --git a/src/main/java/noppes/npcs/attribute/ModAttributes.java b/src/main/java/noppes/npcs/attribute/ModAttributes.java new file mode 100644 index 000000000..f533d755f --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/ModAttributes.java @@ -0,0 +1,21 @@ +package noppes.npcs.attribute; + +/** + * Registers core (non‑magic) attribute definitions. + * For example, main attack damage, health, critical chance, etc. + */ +public class ModAttributes { + public static final AttributeDefinition MAIN_ATTACK_FLAT; + public static final AttributeDefinition MAIN_ATTACK_PERCENT; + public static final AttributeDefinition HEALTH; + public static final AttributeDefinition CRITICAL_CHANCE_PERCENT; + public static final AttributeDefinition CRITICAL_DAMAGE_FLAT; + + static { + MAIN_ATTACK_FLAT = AttributeController.registerAttribute("cnpc:main_attack_flat", "Main Attack Damage (Flat)", Operation.FLAT); + MAIN_ATTACK_PERCENT = AttributeController.registerAttribute("cnpc:main_attack_percent", "Main Attack Damage (%)", Operation.PERCENT); + HEALTH = AttributeController.registerAttribute("cnpc:health", "Health", Operation.FLAT); + CRITICAL_CHANCE_PERCENT = AttributeController.registerAttribute("cnpc:critical_chance_percent", "Critical Chance (%)", Operation.PERCENT); + CRITICAL_DAMAGE_FLAT = AttributeController.registerAttribute("cnpc:critical_damage_flat", "Critical Damage Bonus (Flat)", Operation.FLAT); + } +} diff --git a/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java b/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java new file mode 100644 index 000000000..6fa459283 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java @@ -0,0 +1,66 @@ +package noppes.npcs.attribute; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class ModifiableAttributeInstance implements IAttributeInstance { + private final AttributeDefinition attribute; + private double baseValue; + private final Set modifiers = new HashSet<>(); + + public ModifiableAttributeInstance(AttributeDefinition attribute, double baseValue) { + this.attribute = attribute; + this.baseValue = baseValue; + } + + @Override + public AttributeDefinition getAttribute() { + return attribute; + } + + @Override + public double getBaseValue() { + return baseValue; + } + + @Override + public void setBaseValue(double value) { + this.baseValue = value; + } + + @Override + public void applyModifier(AttributeModifier modifier) { + modifiers.add(modifier); + } + + @Override + public void removeModifier(AttributeModifier modifier) { + modifiers.remove(modifier); + } + + @Override + public Collection getModifiers() { + return modifiers; + } + + @Override + public double getAttributeValue() { + double finalValue = baseValue; + // Apply flat modifiers first: + for (AttributeModifier mod : modifiers) { + if (mod.getOperation() == Operation.FLAT) { + finalValue += mod.getAmount(); + } + } + // Then apply percentage modifiers additively: + double percentSum = 0; + for (AttributeModifier mod : modifiers) { + if (mod.getOperation() == Operation.PERCENT) { + percentSum += mod.getAmount(); + } + } + finalValue *= (1 + percentSum); + return finalValue; + } +} diff --git a/src/main/java/noppes/npcs/attribute/Operation.java b/src/main/java/noppes/npcs/attribute/Operation.java new file mode 100644 index 000000000..6f5d5f0b3 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/Operation.java @@ -0,0 +1,6 @@ +package noppes.npcs.attribute; + +public enum Operation { + FLAT, // e.g., +10 Health or +3 Damage + PERCENT // e.g., +0.25 means +25% bonus +} diff --git a/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java b/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java new file mode 100644 index 000000000..08adb8e91 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java @@ -0,0 +1,31 @@ +package noppes.npcs.attribute; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import net.minecraft.entity.player.EntityPlayer; + +/** + * Central manager mapping player UUIDs to their attribute trackers. + */ +public class PlayerAttributeManager { + private static final Map trackers = new HashMap<>(); + + public static PlayerAttributeTracker getTracker(UUID playerId) { + return trackers.computeIfAbsent(playerId, id -> new PlayerAttributeTracker(id)); + } + + public static void removeTracker(UUID playerId) { + trackers.remove(playerId); + } + + /** + * Should be called every 10 ticks for all online players. + */ + public static void updateAllTrackers(Iterable players) { + for (EntityPlayer player : players) { + PlayerAttributeTracker tracker = getTracker(player.getUniqueID()); + tracker.updateIfChanged(player); + } + } +} diff --git a/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java b/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java new file mode 100644 index 000000000..5c7960251 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java @@ -0,0 +1,21 @@ +package noppes.npcs.attribute; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.entity.player.EntityPlayer; + +/** + * Tick handler that runs every tick and, every 10 ticks, checks all players for equipment changes. + */ +public class PlayerAttributeTickHandler { + private int tickCount = 0; + + public void onTick() { + tickCount++; + if (tickCount % 10 == 0) { + for (Object o : MinecraftServer.getServer().getConfigurationManager().playerEntityList) { + EntityPlayer player = (EntityPlayer) o; + PlayerAttributeManager.getTracker(player.getUniqueID()).updateIfChanged(player); + } + } + } +} diff --git a/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java b/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java new file mode 100644 index 000000000..9b9505f75 --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java @@ -0,0 +1,118 @@ +package noppes.npcs.attribute; + +import java.util.UUID; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import java.util.Map; + +/** + * A tracker that holds a cached aggregation of a player’s attributes (derived from their held item and armor). + * It does not “own” the attributes but recalculates them when equipment changes. + * A simple snapshot of equipment is used to detect changes. + */ +public class PlayerAttributeTracker { + private final UUID playerId; + private CustomAttributeMap aggregatedAttributes = new CustomAttributeMap(); + private String equipmentSnapshot = ""; + + public PlayerAttributeTracker(UUID playerId) { + this.playerId = playerId; + recalcAttributes(null); + } + + public CustomAttributeMap getAggregatedAttributes() { + return aggregatedAttributes; + } + + /** + * Computes a snapshot string representing current equipment. + * For simplicity, concatenates item class names and damage values. + */ + private String computeEquipmentSnapshot(EntityPlayer player) { + StringBuilder sb = new StringBuilder(); + ItemStack held = player.getHeldItem(); + if(held != null) { + sb.append(held.getItem().toString()).append(held.getItemDamage()); + } + for (ItemStack armor : player.inventory.armorInventory) { + if(armor != null) { + sb.append(armor.getItem().toString()).append(armor.getItemDamage()); + } + } + return sb.toString(); + } + + /** + * Recalculates the aggregated attribute map from the player's equipment. + */ + public void recalcAttributes(EntityPlayer player) { + CustomAttributeMap newMap = new CustomAttributeMap(); + // Register base (non-magic) attributes with default values. + newMap.registerAttribute(ModAttributes.MAIN_ATTACK_FLAT, 0.0); + newMap.registerAttribute(ModAttributes.MAIN_ATTACK_PERCENT, 0.0); + newMap.registerAttribute(ModAttributes.HEALTH, 20.0); + newMap.registerAttribute(ModAttributes.CRITICAL_CHANCE_PERCENT, 0.0); + newMap.registerAttribute(ModAttributes.CRITICAL_DAMAGE_FLAT, 0.0); + + if(player != null) { + // Process held item: + ItemStack held = player.getHeldItem(); + if(held != null) { + for (Map.Entry entry : CNPCItemAttributeHelper.readAttributes(held).entrySet()) { + String key = entry.getKey(); + double value = entry.getValue(); + if("cnpc:main_attack_flat".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.MAIN_ATTACK_FLAT); + inst.setBaseValue(inst.getBaseValue() + value); + } else if("cnpc:main_attack_percent".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.MAIN_ATTACK_PERCENT); + inst.setBaseValue(inst.getBaseValue() + value); + } else if("cnpc:critical_chance_percent".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.CRITICAL_CHANCE_PERCENT); + inst.setBaseValue(inst.getBaseValue() + value); + } else if("cnpc:critical_damage_flat".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.CRITICAL_DAMAGE_FLAT); + inst.setBaseValue(inst.getBaseValue() + value); + } + } + } + // Process armor pieces: + for (ItemStack armor : player.inventory.armorInventory) { + if(armor != null) { + for (Map.Entry entry : CNPCItemAttributeHelper.readAttributes(armor).entrySet()) { + String key = entry.getKey(); + double value = entry.getValue(); + if("cnpc:main_attack_flat".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.MAIN_ATTACK_FLAT); + inst.setBaseValue(inst.getBaseValue() + value); + } else if("cnpc:main_attack_percent".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.MAIN_ATTACK_PERCENT); + inst.setBaseValue(inst.getBaseValue() + value); + } else if("cnpc:health".equals(key)) { + IAttributeInstance inst = newMap.getAttributeInstance(ModAttributes.HEALTH); + inst.setBaseValue(inst.getBaseValue() + value); + } + } + } + } + } + aggregatedAttributes = newMap; + } + + /** + * Checks if the player's equipment has changed (by comparing a snapshot) and recalculates if needed. + * This should be called every 10 ticks. + */ + public void updateIfChanged(EntityPlayer player) { + String currentSnapshot = computeEquipmentSnapshot(player); + if (!currentSnapshot.equals(equipmentSnapshot)) { + equipmentSnapshot = currentSnapshot; + recalcAttributes(player); + } + } + + public double getAttributeValue(AttributeDefinition def) { + IAttributeInstance inst = aggregatedAttributes.getAttributeInstance(def); + return inst != null ? inst.getAttributeValue() : 0.0; + } +}