package com.minelittlepony.unicopia.client; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; import javax.annotation.Nullable; import com.google.common.collect.Multimap; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.player.PlayerAttributes; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.enchantment.AttributedEnchantment; import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback; import net.minecraft.client.MinecraftClient; import net.minecraft.client.item.TooltipContext; import net.minecraft.enchantment.Enchantment; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.attribute.EntityAttribute; import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.attribute.EntityAttributeModifier.Operation; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.Pair; import net.minecraft.util.registry.Registry; public class ModifierTooltipRenderer implements ItemTooltipCallback { @Override public void getTooltip(ItemStack stack, TooltipContext context, List lines) { int flags = stack.hasTag() && stack.getTag().contains("HideFlags", 99) ? stack.getTag().getInt("HideFlags") : 0; if (isShowing(flags, ItemStack.TooltipSection.MODIFIERS)) { Map> modifiers = new HashMap<>(); Equine.of(MinecraftClient.getInstance().player).ifPresent(eq -> { getEnchantments(stack).filter(p -> p.getRight() instanceof AttributedEnchantment).forEach(pair -> { ((AttributedEnchantment)pair.getRight()).getModifiers(eq, pair.getLeft(), modifiers); }); }); modifiers.forEach((slot, modifs) -> { List newLines = new ArrayList<>(); modifs.entries().stream() .filter(entry -> entry.getKey().equals(EntityAttributes.GENERIC_MOVEMENT_SPEED) || PlayerAttributes.REGISTRY.contains(entry.getKey())) .forEach(entry -> describeModifiers(entry.getKey(), entry.getValue(), null, newLines)); if (!newLines.isEmpty()) { Text find = new TranslatableText("item.modifiers." + slot.getName()).formatted(Formatting.GRAY); int insertPosition = getInsertPosition(stack, find, flags, lines, context.isAdvanced()); if (insertPosition == -1) { lines.add(LiteralText.EMPTY); lines.add(find); lines.addAll(newLines); } else { lines.addAll(insertPosition, newLines); } } }); } } private int getInsertPosition(ItemStack stack, Text category, int flags, List lines, boolean advanced) { int insertPosition = lines.indexOf(category); if (insertPosition > -1) { return insertPosition + 1; } if (insertPosition == -1 && stack.hasTag()) { if (isShowing(flags, ItemStack.TooltipSection.MODIFIERS) && stack.getTag().getBoolean("Unbreakable")) { insertPosition = checkFor(lines, new TranslatableText("item.unbreakable").formatted(Formatting.BLUE)); } if (insertPosition == -1 && isShowing(flags, ItemStack.TooltipSection.CAN_DESTROY) && stack.getTag().contains("CanDestroy", 9)) { insertPosition = checkFor(lines, new TranslatableText("item.canBreak").formatted(Formatting.GRAY)); } if (insertPosition == -1 && isShowing(flags, ItemStack.TooltipSection.CAN_PLACE) && stack.getTag().contains("CanPlaceOn", 9)) { insertPosition = checkFor(lines, new TranslatableText("item.canPlace").formatted(Formatting.GRAY)); } } if (insertPosition == -1 && advanced) { if (stack.isDamaged()) { insertPosition = checkFor(lines, new TranslatableText("item.durability", stack.getMaxDamage() - stack.getDamage(), stack.getMaxDamage())); } else { insertPosition = checkFor(lines, new LiteralText(Registry.ITEM.getId(stack.getItem()).toString()).formatted(Formatting.DARK_GRAY)); } } return insertPosition; } private int checkFor(List lines, Text category) { return lines.indexOf(category); } private void describeModifiers(EntityAttribute attribute, EntityAttributeModifier modifier, @Nullable PlayerEntity player, List lines) { double value = modifier.getValue(); boolean baseAdjusted = false; if (player != null) { value += player.getAttributeBaseValue(attribute); baseAdjusted = true; } Operation op = modifier.getOperation(); double displayValue; if (op != EntityAttributeModifier.Operation.MULTIPLY_BASE && op != EntityAttributeModifier.Operation.MULTIPLY_TOTAL) { displayValue = value; } else { displayValue = value * 100; } if (baseAdjusted) { lines.add(new LiteralText(" ").append(getModifierLineBase("equals", displayValue, op, attribute, Formatting.DARK_GREEN))); } else if (value > 0) { lines.add(getModifierLineBase("plus", displayValue, op, attribute, attribute == PlayerAttributes.ENTITY_GRAVTY_MODIFIER ? Formatting.RED : Formatting.BLUE)); } else if (value < 0) { lines.add(getModifierLineBase("take", -displayValue, op, attribute, attribute == PlayerAttributes.ENTITY_GRAVTY_MODIFIER ? Formatting.BLUE : Formatting.RED)); } } private Text getModifierLineBase(String root, double displayValue, Operation op, EntityAttribute attribute, Formatting color) { return new TranslatableText("attribute.modifier." + root + "." + op.getId(), ItemStack.MODIFIER_FORMAT.format(displayValue), new TranslatableText(attribute.getTranslationKey()) ).formatted(color); } private static boolean isShowing(int flags, ItemStack.TooltipSection section) { return (flags & section.getFlag()) == 0; } private static Stream> getEnchantments(ItemStack stack) { if (!stack.isEmpty()) { return stack.getEnchantments() .stream() .map(t -> (CompoundTag)t) .map(tag -> Registry.ENCHANTMENT.getOrEmpty(Identifier.tryParse(tag.getString("id"))) .map(ench -> new Pair<>(tag.getInt("lvl"), ench))) .filter(Optional::isPresent) .map(Optional::get); } return Stream.empty(); } }