diff --git a/src/main/java/noppes/npcs/attribute/AttributeController.java b/src/main/java/noppes/npcs/attribute/AttributeController.java index 516620796..505062895 100644 --- a/src/main/java/noppes/npcs/attribute/AttributeController.java +++ b/src/main/java/noppes/npcs/attribute/AttributeController.java @@ -5,19 +5,17 @@ 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. + * AttributeController is the central registry for attribute definitions. + * It ensures that keys are unique. */ public class AttributeController { private static final Map definitions = new HashMap<>(); - private static int nextId = 0; - public static AttributeDefinition registerAttribute(String key, String displayName, AttributeValueType op) { + public static AttributeDefinition registerAttribute(String key, String displayName, AttributeValueType valueType) { if (definitions.containsKey(key)) { - return definitions.get(key); + throw new IllegalArgumentException("Attribute already registered with key: " + key); } - AttributeDefinition def = new AttributeDefinition(nextId++, key, displayName, op); + AttributeDefinition def = new AttributeDefinition(key, displayName, valueType); definitions.put(key, def); return def; } diff --git a/src/main/java/noppes/npcs/attribute/AttributeKeys.java b/src/main/java/noppes/npcs/attribute/AttributeKeys.java new file mode 100644 index 000000000..5707a770a --- /dev/null +++ b/src/main/java/noppes/npcs/attribute/AttributeKeys.java @@ -0,0 +1,17 @@ +package noppes.npcs.attribute; + +public final class AttributeKeys { + public static final String MAIN_ATTACK_FLAT = "cnpc:main_attack_flat"; + public static final String MAIN_ATTACK_PERCENT = "cnpc:main_attack_percent"; + public static final String HEALTH = "cnpc:health"; + public static final String CRITICAL_CHANCE_PERCENT = "cnpc:critical_chance_percent"; + public static final String CRITICAL_DAMAGE_FLAT = "cnpc:critical_damage_flat"; + + // Magic–based attribute keys: + public static final String MAGIC_DAMAGE_FLAT = "cnpc:magic_damage_flat"; + public static final String MAGIC_DAMAGE_PERCENT = "cnpc:magic_damage_percent"; + public static final String MAGIC_DEFENSE_FLAT = "cnpc:magic_defense_flat"; + public static final String MAGIC_RESISTANCE_PERCENT = "cnpc:magic_resistance_percent"; + + private AttributeKeys() { } +} diff --git a/src/main/java/noppes/npcs/attribute/AttributeModifier.java b/src/main/java/noppes/npcs/attribute/AttributeModifier.java index 9401f93c7..ccaed7c15 100644 --- a/src/main/java/noppes/npcs/attribute/AttributeModifier.java +++ b/src/main/java/noppes/npcs/attribute/AttributeModifier.java @@ -3,21 +3,20 @@ 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. + * An AttributeModifier represents a bonus (or penalty) that is applied to an attribute. + * It carries a UUID, a human–readable name, an amount, and a type (FLAT, PERCENT, etc). */ public class AttributeModifier { private final UUID id; private final String name; private final double amount; - private final AttributeValueType attributeValueType; + private final AttributeValueType valueType; - public AttributeModifier(UUID id, String name, double amount, AttributeValueType attributeValueType) { + public AttributeModifier(UUID id, String name, double amount, AttributeValueType valueType) { this.id = id; this.name = name; this.amount = amount; - this.attributeValueType = attributeValueType; + this.valueType = valueType; } public UUID getId() { @@ -32,7 +31,7 @@ public class AttributeModifier { return amount; } - public AttributeValueType getOperation() { - return attributeValueType; + public AttributeValueType getValueType() { + return valueType; } } diff --git a/src/main/java/noppes/npcs/attribute/AttributeValueType.java b/src/main/java/noppes/npcs/attribute/AttributeValueType.java index 6fa3b7ca7..26c35b6ff 100644 --- a/src/main/java/noppes/npcs/attribute/AttributeValueType.java +++ b/src/main/java/noppes/npcs/attribute/AttributeValueType.java @@ -1,6 +1,7 @@ package noppes.npcs.attribute; public enum AttributeValueType { - FLAT, // e.g., +10 Health or +3 Damage - PERCENT // e.g., +0.25 means +25% bonus + FLAT, // e.g., +10 Health or +3 Damage + PERCENT, // e.g., +0.25 means +25% bonus + MAGIC // Represents a container of magic–based values (summed up as a double) } diff --git a/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java b/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java index 44b286773..02a38b4eb 100644 --- a/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java +++ b/src/main/java/noppes/npcs/attribute/CNPCItemAttributeHelper.java @@ -7,22 +7,21 @@ 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". + * Provides helper functions to write/read item attributes to/from NBT. + * Non–magic attributes are stored under a compound tag (TAG_ROOT), + * and magic attributes are stored as a compound mapping (by magic ID) under specific keys. */ 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) { + if (item == null) return; + if (item.stackTagCompound == null) { item.stackTagCompound = new NBTTagCompound(); } NBTTagCompound root = item.stackTagCompound; NBTTagCompound attrTag; - if(root.hasKey(TAG_ROOT)) { + if (root.hasKey(TAG_ROOT)) { attrTag = root.getCompoundTag(TAG_ROOT); } else { attrTag = new NBTTagCompound(); @@ -33,12 +32,12 @@ public class CNPCItemAttributeHelper { public static Map readAttributes(ItemStack item) { Map map = new HashMap<>(); - if(item == null || item.stackTagCompound == null) return map; + if (item == null || item.stackTagCompound == null) return map; NBTTagCompound root = item.stackTagCompound; - if(root.hasKey(TAG_ROOT)) { + if (root.hasKey(TAG_ROOT)) { NBTTagCompound attrTag = root.getCompoundTag(TAG_ROOT); Set keys = attrTag.func_150296_c(); - for(String key : keys) { + for (String key : keys) { map.put(key, attrTag.getDouble(key)); } } @@ -47,15 +46,15 @@ public class CNPCItemAttributeHelper { 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)) { + 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) { + for (String key : keys) { try { int magicId = Integer.parseInt(key); map.put(magicId, magicMap.getDouble(key)); - } catch(NumberFormatException e) { + } catch (NumberFormatException e) { // Skip invalid key. } } @@ -64,11 +63,11 @@ public class CNPCItemAttributeHelper { } public static void writeMagicAttribute(ItemStack item, String attributeTag, int magicId, double value) { - if(item == null) return; - if(item.stackTagCompound == null) + if (item == null) return; + if (item.stackTagCompound == null) item.stackTagCompound = new NBTTagCompound(); NBTTagCompound magicMap; - if(item.stackTagCompound.hasKey(attributeTag)) { + if (item.stackTagCompound.hasKey(attributeTag)) { magicMap = item.stackTagCompound.getCompoundTag(attributeTag); } else { magicMap = new NBTTagCompound(); diff --git a/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java b/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java index bc8d8780a..be4edecff 100644 --- a/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java +++ b/src/main/java/noppes/npcs/attribute/CustomAttributeMap.java @@ -5,7 +5,7 @@ import java.util.HashMap; import java.util.Map; /** - * Holds a collection of attribute instances (for a weapon, item, or aggregated for a player). + * CustomAttributeMap holds a set of attribute instances for an entity (or item). */ public class CustomAttributeMap { private final Map map = new HashMap<>(); diff --git a/src/main/java/noppes/npcs/attribute/DamageCalculator.java b/src/main/java/noppes/npcs/attribute/DamageCalculator.java index c8258b20c..d776b1929 100644 --- a/src/main/java/noppes/npcs/attribute/DamageCalculator.java +++ b/src/main/java/noppes/npcs/attribute/DamageCalculator.java @@ -1,59 +1,39 @@ 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). + * Calculates damage using cached (aggregated) offensive and defensive ratings from players. + * The formula here is simple: baseDamage = attacker.getOffense() - defender.getDefense(), + * then a critical chance is applied (if a random roll is below the attacker's crit chance, + * 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); + /** + * Calculates damage given attacker and defender attribute trackers. + */ + public static double calculateDamage(PlayerAttributeTracker attacker, PlayerAttributeTracker defender) { + double offense = attacker.getOffense(); + double defense = defender.getDefense(); + double damage = offense - defense; - 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; + // Process critical hit chance from attacker's aggregated non-magic attributes. + double critChance = attacker.getAttributeValue(ModAttributes.CRITICAL_CHANCE_PERCENT); + double critBonus = attacker.getAttributeValue(ModAttributes.CRITICAL_DAMAGE_FLAT); + if (random.nextDouble() < critChance) { + damage = damage * 2 + critBonus; } + return Math.max(damage, 0); + } - 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); + /** + * Optionally, calculates damage for a specific magic type. + */ + public static double calculateMagicDamage(PlayerAttributeTracker attacker, PlayerAttributeTracker defender, int magicId) { + double offense = attacker.getOffense(magicId); + double defense = defender.getDefense(magicId); + return Math.max(offense - defense, 0); } } diff --git a/src/main/java/noppes/npcs/attribute/IAttributeInstance.java b/src/main/java/noppes/npcs/attribute/IAttributeInstance.java index 67bab94b5..5aadb8efd 100644 --- a/src/main/java/noppes/npcs/attribute/IAttributeInstance.java +++ b/src/main/java/noppes/npcs/attribute/IAttributeInstance.java @@ -3,8 +3,7 @@ package noppes.npcs.attribute; import java.util.Collection; /** - * Represents an attribute instance – a value (plus any applied modifiers) - * corresponding to a given AttributeDefinition. + * IAttributeInstance represents an instance of an attribute (with a base value and applied modifiers). */ public interface IAttributeInstance { AttributeDefinition getAttribute(); @@ -13,5 +12,5 @@ public interface IAttributeInstance { void applyModifier(AttributeModifier modifier); void removeModifier(AttributeModifier modifier); Collection getModifiers(); - double getAttributeValue(); // Final value after modifiers. + double getAttributeValue(); // Final computed value after modifiers } diff --git a/src/main/java/noppes/npcs/attribute/ModAttributes.java b/src/main/java/noppes/npcs/attribute/ModAttributes.java index 4cc11339e..1000e8efe 100644 --- a/src/main/java/noppes/npcs/attribute/ModAttributes.java +++ b/src/main/java/noppes/npcs/attribute/ModAttributes.java @@ -1,8 +1,7 @@ package noppes.npcs.attribute; /** - * Registers core (non‑magic) attribute definitions. - * For example, main attack damage, health, critical chance, etc. + * ModAttributes registers the core attributes (both non–magic and magic–based). */ public class ModAttributes { public static final AttributeDefinition MAIN_ATTACK_FLAT; @@ -11,11 +10,21 @@ public class ModAttributes { public static final AttributeDefinition CRITICAL_CHANCE_PERCENT; public static final AttributeDefinition CRITICAL_DAMAGE_FLAT; + public static final AttributeDefinition MAGIC_DAMAGE_FLAT; + public static final AttributeDefinition MAGIC_DAMAGE_PERCENT; + public static final AttributeDefinition MAGIC_DEFENSE_FLAT; + public static final AttributeDefinition MAGIC_RESISTANCE_PERCENT; + static { - MAIN_ATTACK_FLAT = AttributeController.registerAttribute("cnpc:main_attack_flat", "Main Attack Damage (Flat)", AttributeValueType.FLAT); - MAIN_ATTACK_PERCENT = AttributeController.registerAttribute("cnpc:main_attack_percent", "Main Attack Damage (%)", AttributeValueType.PERCENT); - HEALTH = AttributeController.registerAttribute("cnpc:health", "Health", AttributeValueType.FLAT); - CRITICAL_CHANCE_PERCENT = AttributeController.registerAttribute("cnpc:critical_chance_percent", "Critical Chance (%)", AttributeValueType.PERCENT); - CRITICAL_DAMAGE_FLAT = AttributeController.registerAttribute("cnpc:critical_damage_flat", "Critical Damage Bonus (Flat)", AttributeValueType.FLAT); + MAIN_ATTACK_FLAT = AttributeController.registerAttribute(AttributeKeys.MAIN_ATTACK_FLAT, "Main Attack Damage (Flat)", AttributeValueType.FLAT); + MAIN_ATTACK_PERCENT = AttributeController.registerAttribute(AttributeKeys.MAIN_ATTACK_PERCENT, "Main Attack Damage (%)", AttributeValueType.PERCENT); + HEALTH = AttributeController.registerAttribute(AttributeKeys.HEALTH, "Health", AttributeValueType.FLAT); + CRITICAL_CHANCE_PERCENT = AttributeController.registerAttribute(AttributeKeys.CRITICAL_CHANCE_PERCENT, "Critical Chance (%)", AttributeValueType.PERCENT); + CRITICAL_DAMAGE_FLAT = AttributeController.registerAttribute(AttributeKeys.CRITICAL_DAMAGE_FLAT, "Critical Damage Bonus (Flat)", AttributeValueType.FLAT); + + MAGIC_DAMAGE_FLAT = AttributeController.registerAttribute(AttributeKeys.MAGIC_DAMAGE_FLAT, "Magic Damage (Flat)", AttributeValueType.MAGIC); + MAGIC_DAMAGE_PERCENT = AttributeController.registerAttribute(AttributeKeys.MAGIC_DAMAGE_PERCENT, "Magic Damage (%)", AttributeValueType.MAGIC); + MAGIC_DEFENSE_FLAT = AttributeController.registerAttribute(AttributeKeys.MAGIC_DEFENSE_FLAT, "Magic Defense (Flat)", AttributeValueType.MAGIC); + MAGIC_RESISTANCE_PERCENT = AttributeController.registerAttribute(AttributeKeys.MAGIC_RESISTANCE_PERCENT, "Magic Resistance (%)", AttributeValueType.MAGIC); } } diff --git a/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java b/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java index 2a2344236..5645165c3 100644 --- a/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java +++ b/src/main/java/noppes/npcs/attribute/ModifiableAttributeInstance.java @@ -47,16 +47,16 @@ public class ModifiableAttributeInstance implements IAttributeInstance { @Override public double getAttributeValue() { double finalValue = baseValue; - // Apply flat modifiers first: + // Apply FLAT modifiers for (AttributeModifier mod : modifiers) { - if (mod.getOperation() == AttributeValueType.FLAT) { + if (mod.getValueType() == AttributeValueType.FLAT) { finalValue += mod.getAmount(); } } - // Then apply percentage modifiers additively: - double percentSum = 0; + // Apply PERCENT modifiers additively. + double percentSum = 0.0; for (AttributeModifier mod : modifiers) { - if (mod.getOperation() == AttributeValueType.PERCENT) { + if (mod.getValueType() == AttributeValueType.PERCENT) { percentSum += mod.getAmount(); } } diff --git a/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java b/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java index 08adb8e91..9b6a3dbef 100644 --- a/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java +++ b/src/main/java/noppes/npcs/attribute/PlayerAttributeManager.java @@ -6,7 +6,7 @@ import java.util.UUID; import net.minecraft.entity.player.EntityPlayer; /** - * Central manager mapping player UUIDs to their attribute trackers. + * PlayerAttributeManager maps player UUIDs to their attribute trackers. */ public class PlayerAttributeManager { private static final Map trackers = new HashMap<>(); @@ -20,7 +20,7 @@ public class PlayerAttributeManager { } /** - * Should be called every 10 ticks for all online players. + * Update all trackers (e.g. called every 10 ticks for all online players). */ public static void updateAllTrackers(Iterable players) { for (EntityPlayer player : players) { diff --git a/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java b/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java index 5c7960251..a65f70479 100644 --- a/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java +++ b/src/main/java/noppes/npcs/attribute/PlayerAttributeTickHandler.java @@ -4,7 +4,8 @@ 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. + * Tick handler that runs every tick and, every 10 ticks, checks all players + * for equipment changes to update their attribute trackers. */ public class PlayerAttributeTickHandler { private int tickCount = 0; diff --git a/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java b/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java index 9b9505f75..b11bea45a 100644 --- a/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java +++ b/src/main/java/noppes/npcs/attribute/PlayerAttributeTracker.java @@ -1,18 +1,30 @@ package noppes.npcs.attribute; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; -import java.util.Map; +import java.util.Map.Entry; /** - * 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. + * Aggregates a player's attributes based on their held item and armor. + * Non‑magic attributes are stored in a CustomAttributeMap. + * Magic–based attributes (offense and defense) are aggregated as maps: + * for each magic type, we sum the FLAT and PERCENT values. + * + * This tracker is updated (for example, every 10 ticks) if the player's equipment changes. */ public class PlayerAttributeTracker { private final UUID playerId; private CustomAttributeMap aggregatedAttributes = new CustomAttributeMap(); + // Aggregated magic offensive values from items (weapon, etc.) + private final Map aggregatedMagicDamageFlat = new HashMap<>(); + private final Map aggregatedMagicDamagePercent = new HashMap<>(); + // Aggregated magic defensive values from armor + private final Map aggregatedMagicDefenseFlat = new HashMap<>(); + private final Map aggregatedMagicResistancePercent = new HashMap<>(); + private String equipmentSnapshot = ""; public PlayerAttributeTracker(UUID playerId) { @@ -25,17 +37,16 @@ public class PlayerAttributeTracker { } /** - * Computes a snapshot string representing current equipment. - * For simplicity, concatenates item class names and damage values. + * Builds a snapshot string from the player's held item and armor. */ private String computeEquipmentSnapshot(EntityPlayer player) { StringBuilder sb = new StringBuilder(); ItemStack held = player.getHeldItem(); - if(held != null) { + if (held != null) { sb.append(held.getItem().toString()).append(held.getItemDamage()); } for (ItemStack armor : player.inventory.armorInventory) { - if(armor != null) { + if (armor != null) { sb.append(armor.getItem().toString()).append(armor.getItemDamage()); } } @@ -43,65 +54,96 @@ public class PlayerAttributeTracker { } /** - * Recalculates the aggregated attribute map from the player's equipment. + * Recalculates the aggregated attributes from the player's equipment. + * Non‑magic attributes are processed into a CustomAttributeMap, + * while magic attributes (offense and defense) are aggregated into separate maps. */ public void recalcAttributes(EntityPlayer player) { + // Reinitialize non-magic attributes: 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); + aggregatedAttributes = newMap; - if(player != null) { - // Process held item: + // Reset magic aggregates: + aggregatedMagicDamageFlat.clear(); + aggregatedMagicDamagePercent.clear(); + aggregatedMagicDefenseFlat.clear(); + aggregatedMagicResistancePercent.clear(); + + if (player != null) { + // Process held item (assumed to be offense—weapon): ItemStack held = player.getHeldItem(); - if(held != null) { - for (Map.Entry entry : CNPCItemAttributeHelper.readAttributes(held).entrySet()) { + if (held != null) { + // Non-magic: + for (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); + if (AttributeKeys.MAIN_ATTACK_FLAT.equals(key)) { + IAttributeInstance inst = aggregatedAttributes.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); + } else if (AttributeKeys.MAIN_ATTACK_PERCENT.equals(key)) { + IAttributeInstance inst = aggregatedAttributes.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); + } else if (AttributeKeys.CRITICAL_CHANCE_PERCENT.equals(key)) { + IAttributeInstance inst = aggregatedAttributes.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); + } else if (AttributeKeys.CRITICAL_DAMAGE_FLAT.equals(key)) { + IAttributeInstance inst = aggregatedAttributes.getAttributeInstance(ModAttributes.CRITICAL_DAMAGE_FLAT); inst.setBaseValue(inst.getBaseValue() + value); } } + // Magic offense: + Map weaponMagicFlat = CNPCItemAttributeHelper.readMagicAttributeMap(held, AttributeKeys.MAGIC_DAMAGE_FLAT); + for (Entry entry : weaponMagicFlat.entrySet()) { + int magicId = entry.getKey(); + double value = entry.getValue(); + aggregatedMagicDamageFlat.put(magicId, aggregatedMagicDamageFlat.getOrDefault(magicId, 0.0) + value); + } + Map weaponMagicPercent = CNPCItemAttributeHelper.readMagicAttributeMap(held, AttributeKeys.MAGIC_DAMAGE_PERCENT); + for (Entry entry : weaponMagicPercent.entrySet()) { + int magicId = entry.getKey(); + double value = entry.getValue(); + aggregatedMagicDamagePercent.put(magicId, aggregatedMagicDamagePercent.getOrDefault(magicId, 0.0) + value); + } } - // Process armor pieces: + // Process armor (assumed to contribute defense): for (ItemStack armor : player.inventory.armorInventory) { - if(armor != null) { - for (Map.Entry entry : CNPCItemAttributeHelper.readAttributes(armor).entrySet()) { + if (armor != null) { + // Non-magic: + for (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); + if (AttributeKeys.HEALTH.equals(key)) { + IAttributeInstance inst = aggregatedAttributes.getAttributeInstance(ModAttributes.HEALTH); inst.setBaseValue(inst.getBaseValue() + value); } + // Could add other non-magic defensive bonuses here. + } + // Magic defense: + Map armorMagicFlat = CNPCItemAttributeHelper.readMagicAttributeMap(armor, AttributeKeys.MAGIC_DEFENSE_FLAT); + for (Entry entry : armorMagicFlat.entrySet()) { + int magicId = entry.getKey(); + double value = entry.getValue(); + aggregatedMagicDefenseFlat.put(magicId, aggregatedMagicDefenseFlat.getOrDefault(magicId, 0.0) + value); + } + Map armorMagicPercent = CNPCItemAttributeHelper.readMagicAttributeMap(armor, AttributeKeys.MAGIC_RESISTANCE_PERCENT); + for (Entry entry : armorMagicPercent.entrySet()) { + int magicId = entry.getKey(); + double value = entry.getValue(); + aggregatedMagicResistancePercent.put(magicId, aggregatedMagicResistancePercent.getOrDefault(magicId, 0.0) + 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. + * Checks if the player's equipment has changed (using a snapshot) and recalculates if so. + * Should be called periodically (e.g. every 10 ticks). */ public void updateIfChanged(EntityPlayer player) { String currentSnapshot = computeEquipmentSnapshot(player); @@ -111,8 +153,63 @@ public class PlayerAttributeTracker { } } + /** + * Returns the aggregated non-magic value for a given attribute. + */ public double getAttributeValue(AttributeDefinition def) { IAttributeInstance inst = aggregatedAttributes.getAttributeInstance(def); return inst != null ? inst.getAttributeValue() : 0.0; } + + /** + * Returns overall offensive rating: + * Non-magic offense (MAIN_ATTACK_FLAT*(1+MAIN_ATTACK_PERCENT)) + * plus the sum over all magic offense: for each magic type, flat*(1+percent). + */ + public double getOffense() { + double nonMagic = getAttributeValue(ModAttributes.MAIN_ATTACK_FLAT) * (1 + getAttributeValue(ModAttributes.MAIN_ATTACK_PERCENT)); + double magicOffense = 0.0; + for (Map.Entry entry : aggregatedMagicDamageFlat.entrySet()) { + int magicId = entry.getKey(); + double flat = entry.getValue(); + double percent = aggregatedMagicDamagePercent.getOrDefault(magicId, 0.0); + magicOffense += flat * (1 + percent); + } + return nonMagic + magicOffense; + } + + /** + * Returns offensive value for a specific magic type. + */ + public double getOffense(int magicId) { + double flat = aggregatedMagicDamageFlat.getOrDefault(magicId, 0.0); + double percent = aggregatedMagicDamagePercent.getOrDefault(magicId, 0.0); + return flat * (1 + percent); + } + + /** + * Returns overall defensive rating: + * Non-magic defense is given by HEALTH. + * Plus sum over all magic defense: for each magic type, flat*(1+percent). + */ + public double getDefense() { + double nonMagic = getAttributeValue(ModAttributes.HEALTH); + double magicDefense = 0.0; + for (Map.Entry entry : aggregatedMagicDefenseFlat.entrySet()) { + int magicId = entry.getKey(); + double flat = entry.getValue(); + double percent = aggregatedMagicResistancePercent.getOrDefault(magicId, 0.0); + magicDefense += flat * (1 + percent); + } + return nonMagic + magicDefense; + } + + /** + * Returns defensive value for a specific magic type. + */ + public double getDefense(int magicId) { + double flat = aggregatedMagicDefenseFlat.getOrDefault(magicId, 0.0); + double percent = aggregatedMagicResistancePercent.getOrDefault(magicId, 0.0); + return flat * (1 + percent); + } }