create-mekanism-modular/src/main/java/eu/abdelbaki/cmmodular/EquipmentAttributes.java

99 lines
4.1 KiB
Java

package eu.abdelbaki.cmmodular;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import net.minecraft.core.Holder;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeMap;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
/**
* Keeps an attribute that a piece of equipment grants from quietly going
* missing.
*
* <p>Modifiers off worn and held items are transient: vanilla reads them when
* the equipment changes and puts them in the entity's attribute map, and that
* map is the only place they live. Change dimension and they can be gone - the
* player is rebuilt on the way through, no equipment change follows, and so
* nothing puts them back until the piece is taken off and put on again. Cinder
* essence's fire immunity is the one that gets noticed, because the Nether is
* exactly where it was wanted.
*
* <p>Two answers, because a modifier can be missing in two places. The map is
* rebuilt from the equipment after a dimension change or a respawn, which is
* what re-equipping would have done and which fixes every attribute rather than
* only this mod's. And when one of this mod's own attributes is read, the
* equipment is consulted as a fallback - the same data vanilla applies from, so
* the answer is the same whether or not the map ever lost it.
*/
public final class EquipmentAttributes {
private EquipmentAttributes() {
}
static void register(IEventBus modBus) {
NeoForge.EVENT_BUS.addListener(EquipmentAttributes::onChangedDimension);
NeoForge.EVENT_BUS.addListener(EquipmentAttributes::onRespawn);
}
private static void onChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
reapply(event.getEntity());
}
private static void onRespawn(PlayerEvent.PlayerRespawnEvent event) {
reapply(event.getEntity());
}
/** Puts every modifier the worn and held items grant back into the map. */
private static void reapply(Player player) {
AttributeMap attributes = player.getAttributes();
for (EquipmentSlot slot : EquipmentSlot.values()) {
ItemStack stack = player.getItemBySlot(slot);
if (stack.isEmpty()) {
continue;
}
Multimap<Holder<Attribute>, AttributeModifier> modifiers = HashMultimap.create();
stack.forEachModifier(slot, modifiers::put);
// Each modifier is removed and added again, so this is a no-op
// wherever one survived and a repair wherever one did not.
attributes.addTransientAttributeModifiers(modifiers);
}
}
/**
* What one of this mod's attributes is worth on this entity, falling back
* to the equipment when the attribute map has lost it.
*
* <p>Only additive modifiers are counted, which is all this mod grants.
*/
static double value(LivingEntity entity, Holder<Attribute> attribute) {
AttributeInstance instance = entity.getAttribute(attribute);
double live = instance == null ? 0.0D : instance.getValue();
if (live > 0.0D) {
return live;
}
double[] worn = {0.0D};
for (EquipmentSlot slot : EquipmentSlot.values()) {
ItemStack stack = entity.getItemBySlot(slot);
if (stack.isEmpty()) {
continue;
}
stack.forEachModifier(slot, (holder, modifier) -> {
if (holder.value() == attribute.value()
&& modifier.operation() == AttributeModifier.Operation.ADD_VALUE) {
worn[0] += modifier.amount();
}
});
}
return worn[0];
}
}