330 lines
13 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|