package net.minecraft.world.item.component; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.List; import java.util.Locale; import java.util.function.BiConsumer; import net.minecraft.Util; import net.minecraft.core.Holder; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.EquipmentSlotGroup; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.AttributeModifier; public record ItemAttributeModifiers(List modifiers) { public static final ItemAttributeModifiers EMPTY = new ItemAttributeModifiers(List.of()); public static final Codec CODEC = ItemAttributeModifiers.Entry.CODEC .listOf() .xmap(ItemAttributeModifiers::new, ItemAttributeModifiers::modifiers); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( ItemAttributeModifiers.Entry.STREAM_CODEC.apply(ByteBufCodecs.list()), ItemAttributeModifiers::modifiers, ItemAttributeModifiers::new ); public static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = Util.make( new DecimalFormat("#.##"), decimalFormat -> decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)) ); public static ItemAttributeModifiers.Builder builder() { return new ItemAttributeModifiers.Builder(); } public ItemAttributeModifiers withModifierAdded(Holder attribute, AttributeModifier modifier, EquipmentSlotGroup slot) { ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(this.modifiers.size() + 1); for (ItemAttributeModifiers.Entry entry : this.modifiers) { if (!entry.matches(attribute, modifier.id())) { builder.add(entry); } } builder.add(new ItemAttributeModifiers.Entry(attribute, modifier, slot)); return new ItemAttributeModifiers(builder.build()); } public void forEach(EquipmentSlotGroup slotGroup, BiConsumer, AttributeModifier> action) { for (ItemAttributeModifiers.Entry entry : this.modifiers) { if (entry.slot.equals(slotGroup)) { action.accept(entry.attribute, entry.modifier); } } } public void forEach(EquipmentSlot equipmentSlot, BiConsumer, AttributeModifier> action) { for (ItemAttributeModifiers.Entry entry : this.modifiers) { if (entry.slot.test(equipmentSlot)) { action.accept(entry.attribute, entry.modifier); } } } public double compute(double baseValue, EquipmentSlot equipmentSlot) { double d = baseValue; for (ItemAttributeModifiers.Entry entry : this.modifiers) { if (entry.slot.test(equipmentSlot)) { double e = entry.modifier.amount(); d += switch (entry.modifier.operation()) { case ADD_VALUE -> e; case ADD_MULTIPLIED_BASE -> e * baseValue; case ADD_MULTIPLIED_TOTAL -> e * d; }; } } return d; } public static class Builder { private final ImmutableList.Builder entries = ImmutableList.builder(); Builder() { } public ItemAttributeModifiers.Builder add(Holder attribute, AttributeModifier modifier, EquipmentSlotGroup slot) { this.entries.add(new ItemAttributeModifiers.Entry(attribute, modifier, slot)); return this; } public ItemAttributeModifiers build() { return new ItemAttributeModifiers(this.entries.build()); } } public record Entry(Holder attribute, AttributeModifier modifier, EquipmentSlotGroup slot) { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( Attribute.CODEC.fieldOf("type").forGetter(ItemAttributeModifiers.Entry::attribute), AttributeModifier.MAP_CODEC.forGetter(ItemAttributeModifiers.Entry::modifier), EquipmentSlotGroup.CODEC.optionalFieldOf("slot", EquipmentSlotGroup.ANY).forGetter(ItemAttributeModifiers.Entry::slot) ) .apply(instance, ItemAttributeModifiers.Entry::new) ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( Attribute.STREAM_CODEC, ItemAttributeModifiers.Entry::attribute, AttributeModifier.STREAM_CODEC, ItemAttributeModifiers.Entry::modifier, EquipmentSlotGroup.STREAM_CODEC, ItemAttributeModifiers.Entry::slot, ItemAttributeModifiers.Entry::new ); public boolean matches(Holder attribute, ResourceLocation id) { return attribute.equals(this.attribute) && this.modifier.is(id); } } }