minecraft-src/net/minecraft/world/item/component/ItemAttributeModifiers.java
2025-09-18 12:27:44 +00:00

330 lines
13 KiB
Java

package net.minecraft.world.item.component;
import com.google.common.collect.ImmutableList;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.buffer.ByteBuf;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ByIdMap;
import net.minecraft.util.StringRepresentable;
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;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import org.apache.commons.lang3.function.TriConsumer;
import org.jetbrains.annotations.Nullable;
public record ItemAttributeModifiers(List<ItemAttributeModifiers.Entry> modifiers) {
public static final ItemAttributeModifiers EMPTY = new ItemAttributeModifiers(List.of());
public static final Codec<ItemAttributeModifiers> CODEC = ItemAttributeModifiers.Entry.CODEC
.listOf()
.xmap(ItemAttributeModifiers::new, ItemAttributeModifiers::modifiers);
public static final StreamCodec<RegistryFriendlyByteBuf, ItemAttributeModifiers> 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> attribute, AttributeModifier modifier, EquipmentSlotGroup slot) {
ImmutableList.Builder<ItemAttributeModifiers.Entry> 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 slot, TriConsumer<Holder<Attribute>, AttributeModifier, ItemAttributeModifiers.Display> action) {
for (ItemAttributeModifiers.Entry entry : this.modifiers) {
if (entry.slot.equals(slot)) {
action.accept(entry.attribute, entry.modifier, entry.display);
}
}
}
public void forEach(EquipmentSlotGroup slotGroup, BiConsumer<Holder<Attribute>, 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<Holder<Attribute>, 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<ItemAttributeModifiers.Entry> entries = ImmutableList.builder();
Builder() {
}
public ItemAttributeModifiers.Builder add(Holder<Attribute> attribute, AttributeModifier modifier, EquipmentSlotGroup slot) {
this.entries.add(new ItemAttributeModifiers.Entry(attribute, modifier, slot));
return this;
}
public ItemAttributeModifiers.Builder add(
Holder<Attribute> attribute, AttributeModifier modifier, EquipmentSlotGroup slot, ItemAttributeModifiers.Display display
) {
this.entries.add(new ItemAttributeModifiers.Entry(attribute, modifier, slot, display));
return this;
}
public ItemAttributeModifiers build() {
return new ItemAttributeModifiers(this.entries.build());
}
}
public interface Display {
Codec<ItemAttributeModifiers.Display> CODEC = ItemAttributeModifiers.Display.Type.CODEC
.dispatch("type", ItemAttributeModifiers.Display::type, type -> type.codec);
StreamCodec<RegistryFriendlyByteBuf, ItemAttributeModifiers.Display> STREAM_CODEC = ItemAttributeModifiers.Display.Type.STREAM_CODEC
.<RegistryFriendlyByteBuf>cast()
.dispatch(ItemAttributeModifiers.Display::type, ItemAttributeModifiers.Display.Type::streamCodec);
static ItemAttributeModifiers.Display attributeModifiers() {
return ItemAttributeModifiers.Display.Default.INSTANCE;
}
static ItemAttributeModifiers.Display hidden() {
return ItemAttributeModifiers.Display.Hidden.INSTANCE;
}
static ItemAttributeModifiers.Display override(Component component) {
return new ItemAttributeModifiers.Display.OverrideText(component);
}
ItemAttributeModifiers.Display.Type type();
void apply(Consumer<Component> output, @Nullable Player player, Holder<Attribute> attribute, AttributeModifier modifier);
public record Default() implements ItemAttributeModifiers.Display {
static final ItemAttributeModifiers.Display.Default INSTANCE = new ItemAttributeModifiers.Display.Default();
static final MapCodec<ItemAttributeModifiers.Display.Default> CODEC = MapCodec.unit(INSTANCE);
static final StreamCodec<RegistryFriendlyByteBuf, ItemAttributeModifiers.Display.Default> STREAM_CODEC = StreamCodec.unit(INSTANCE);
@Override
public ItemAttributeModifiers.Display.Type type() {
return ItemAttributeModifiers.Display.Type.DEFAULT;
}
@Override
public void apply(Consumer<Component> output, @Nullable Player player, Holder<Attribute> attribute, AttributeModifier modifier) {
double d = modifier.amount();
boolean bl = false;
if (player != null) {
if (modifier.is(Item.BASE_ATTACK_DAMAGE_ID)) {
d += player.getAttributeBaseValue(Attributes.ATTACK_DAMAGE);
bl = true;
} else if (modifier.is(Item.BASE_ATTACK_SPEED_ID)) {
d += player.getAttributeBaseValue(Attributes.ATTACK_SPEED);
bl = true;
}
}
double e;
if (modifier.operation() == AttributeModifier.Operation.ADD_MULTIPLIED_BASE || modifier.operation() == AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL) {
e = d * 100.0;
} else if (attribute.is(Attributes.KNOCKBACK_RESISTANCE)) {
e = d * 10.0;
} else {
e = d;
}
if (bl) {
output.accept(
CommonComponents.space()
.append(
Component.translatable(
"attribute.modifier.equals." + modifier.operation().id(),
ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(e),
Component.translatable(attribute.value().getDescriptionId())
)
)
.withStyle(ChatFormatting.DARK_GREEN)
);
} else if (d > 0.0) {
output.accept(
Component.translatable(
"attribute.modifier.plus." + modifier.operation().id(),
ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(e),
Component.translatable(attribute.value().getDescriptionId())
)
.withStyle(attribute.value().getStyle(true))
);
} else if (d < 0.0) {
output.accept(
Component.translatable(
"attribute.modifier.take." + modifier.operation().id(),
ItemAttributeModifiers.ATTRIBUTE_MODIFIER_FORMAT.format(-e),
Component.translatable(attribute.value().getDescriptionId())
)
.withStyle(attribute.value().getStyle(false))
);
}
}
}
public record Hidden() implements ItemAttributeModifiers.Display {
static final ItemAttributeModifiers.Display.Hidden INSTANCE = new ItemAttributeModifiers.Display.Hidden();
static final MapCodec<ItemAttributeModifiers.Display.Hidden> CODEC = MapCodec.unit(INSTANCE);
static final StreamCodec<RegistryFriendlyByteBuf, ItemAttributeModifiers.Display.Hidden> STREAM_CODEC = StreamCodec.unit(INSTANCE);
@Override
public ItemAttributeModifiers.Display.Type type() {
return ItemAttributeModifiers.Display.Type.HIDDEN;
}
@Override
public void apply(Consumer<Component> output, @Nullable Player player, Holder<Attribute> attribute, AttributeModifier modifier) {
}
}
public record OverrideText(Component component) implements ItemAttributeModifiers.Display {
static final MapCodec<ItemAttributeModifiers.Display.OverrideText> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(ComponentSerialization.CODEC.fieldOf("value").forGetter(ItemAttributeModifiers.Display.OverrideText::component))
.apply(instance, ItemAttributeModifiers.Display.OverrideText::new)
);
static final StreamCodec<RegistryFriendlyByteBuf, ItemAttributeModifiers.Display.OverrideText> STREAM_CODEC = StreamCodec.composite(
ComponentSerialization.STREAM_CODEC, ItemAttributeModifiers.Display.OverrideText::component, ItemAttributeModifiers.Display.OverrideText::new
);
@Override
public ItemAttributeModifiers.Display.Type type() {
return ItemAttributeModifiers.Display.Type.OVERRIDE;
}
@Override
public void apply(Consumer<Component> output, @Nullable Player player, Holder<Attribute> attribute, AttributeModifier modifier) {
output.accept(this.component);
}
}
public static enum Type implements StringRepresentable {
DEFAULT("default", 0, ItemAttributeModifiers.Display.Default.CODEC, ItemAttributeModifiers.Display.Default.STREAM_CODEC),
HIDDEN("hidden", 1, ItemAttributeModifiers.Display.Hidden.CODEC, ItemAttributeModifiers.Display.Hidden.STREAM_CODEC),
OVERRIDE("override", 2, ItemAttributeModifiers.Display.OverrideText.CODEC, ItemAttributeModifiers.Display.OverrideText.STREAM_CODEC);
static final Codec<ItemAttributeModifiers.Display.Type> CODEC = StringRepresentable.fromEnum(ItemAttributeModifiers.Display.Type::values);
private static final IntFunction<ItemAttributeModifiers.Display.Type> BY_ID = ByIdMap.continuous(
ItemAttributeModifiers.Display.Type::id, values(), ByIdMap.OutOfBoundsStrategy.ZERO
);
static final StreamCodec<ByteBuf, ItemAttributeModifiers.Display.Type> STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, ItemAttributeModifiers.Display.Type::id);
private final String name;
private final int id;
final MapCodec<? extends ItemAttributeModifiers.Display> codec;
private final StreamCodec<RegistryFriendlyByteBuf, ? extends ItemAttributeModifiers.Display> streamCodec;
private Type(
final String name,
final int id,
final MapCodec<? extends ItemAttributeModifiers.Display> codec,
final StreamCodec<RegistryFriendlyByteBuf, ? extends ItemAttributeModifiers.Display> streamCodec
) {
this.name = name;
this.id = id;
this.codec = codec;
this.streamCodec = streamCodec;
}
@Override
public String getSerializedName() {
return this.name;
}
private int id() {
return this.id;
}
private StreamCodec<RegistryFriendlyByteBuf, ? extends ItemAttributeModifiers.Display> streamCodec() {
return this.streamCodec;
}
}
}
public record Entry(Holder<Attribute> attribute, AttributeModifier modifier, EquipmentSlotGroup slot, ItemAttributeModifiers.Display display) {
public static final Codec<ItemAttributeModifiers.Entry> 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),
ItemAttributeModifiers.Display.CODEC
.optionalFieldOf("display", ItemAttributeModifiers.Display.Default.INSTANCE)
.forGetter(ItemAttributeModifiers.Entry::display)
)
.apply(instance, ItemAttributeModifiers.Entry::new)
);
public static final StreamCodec<RegistryFriendlyByteBuf, ItemAttributeModifiers.Entry> STREAM_CODEC = StreamCodec.composite(
Attribute.STREAM_CODEC,
ItemAttributeModifiers.Entry::attribute,
AttributeModifier.STREAM_CODEC,
ItemAttributeModifiers.Entry::modifier,
EquipmentSlotGroup.STREAM_CODEC,
ItemAttributeModifiers.Entry::slot,
ItemAttributeModifiers.Display.STREAM_CODEC,
ItemAttributeModifiers.Entry::display,
ItemAttributeModifiers.Entry::new
);
public Entry(Holder<Attribute> attribute, AttributeModifier modifier, EquipmentSlotGroup slot) {
this(attribute, modifier, slot, ItemAttributeModifiers.Display.attributeModifiers());
}
public boolean matches(Holder<Attribute> attribute, ResourceLocation id) {
return attribute.equals(this.attribute) && this.modifier.is(id);
}
}
}