Large Unused Attribute Push

This commit is contained in:
Kam committed 2025-02-24 16:08:22 -05:00
1 parent 4faa851078
commit 5818060656
13 files changed
+553

No files matched your search

@@ -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<String, AttributeDefinition> 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<AttributeDefinition> getAllAttributes() {
return definitions.values();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<String, Double> readAttributes(ItemStack item) {
Map<String, Double> 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<String> keys = attrTag.func_150296_c();
for(String key : keys) {
map.put(key, attrTag.getDouble(key));
}
}
return map;
}
public static Map<Integer, Double> readMagicAttributeMap(ItemStack item, String attributeTag) {
Map<Integer, Double> map = new HashMap<>();
if(item == null || item.stackTagCompound == null) return map;
if(item.stackTagCompound.hasKey(attributeTag)) {
NBTTagCompound magicMap = item.stackTagCompound.getCompoundTag(attributeTag);
Set<String> 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);
}
}
@@ -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<AttributeDefinition, IAttributeInstance> 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<IAttributeInstance> getAllAttributes() {
return map.values();
}
}
@@ -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<ItemStack> armorSet) {
// Read non-magic weapon attributes:
Map<String, Double> 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<Integer, Double> weaponMagicFlat = CNPCItemAttributeHelper.readMagicAttributeMap(weapon, "cnpc:magic_damage_flat");
Map<Integer, Double> weaponMagicPercent = CNPCItemAttributeHelper.readMagicAttributeMap(weapon, "cnpc:magic_damage_percent");
for(Map.Entry<Integer, Double> 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<Integer, Double> armorDefFlat = CNPCItemAttributeHelper.readMagicAttributeMap(armor, "cnpc:magic_defense_flat");
Map<Integer, Double> 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);
}
}
@@ -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<AttributeModifier> getModifiers();
double getAttributeValue(); // Final value after modifiers.
}
@@ -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);
}
}
@@ -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<AttributeModifier> 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<AttributeModifier> 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;
}
}
@@ -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
}
@@ -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<UUID, PlayerAttributeTracker> 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<EntityPlayer> players) {
for (EntityPlayer player : players) {
PlayerAttributeTracker tracker = getTracker(player.getUniqueID());
tracker.updateIfChanged(player);
}
}
}
@@ -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);
}
}
}
}
@@ -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<String, Double> 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<String, Double> 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;
}
}