Skip to content

Instantly share code, notes, and snippets.

@SiverDX
Last active February 10, 2024 14:14
Show Gist options
  • Save SiverDX/d09f4568b23f13ceddc0e19f1d1be5b4 to your computer and use it in GitHub Desktop.
Save SiverDX/d09f4568b23f13ceddc0e19f1d1be5b4 to your computer and use it in GitHub Desktop.
apoth curios tooltips
package de.cadentem.trialanderror.core.mods.apotheosis;
import com.google.common.collect.Multimap;
import de.cadentem.trialanderror.mixin.apotheosis.AttributesLibClientAccess;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import shadows.apotheosis.adventure.AdventureModule;
import shadows.apotheosis.core.attributeslib.api.AddAttributeTooltipsEvent;
import shadows.apotheosis.core.attributeslib.api.AttributeHelper;
import top.theillusivec4.curios.api.CuriosApi;
import top.theillusivec4.curios.api.SlotContext;
import top.theillusivec4.curios.common.CuriosHelper;
import java.util.*;
import static net.minecraft.world.item.ItemStack.ATTRIBUTE_MODIFIER_FORMAT;
public class ApothCuriosTooltipHandler {
public record StoredData(ItemTooltipEvent event, List<String> slots, List<Component> attributeTooltip, int firstIndex, int secondIndex) { }
public static void handleTooltip(final StoredData storedData) {
ItemTooltipEvent event = storedData.event();
ItemStack stack = event.getItemStack();
if (!(stack.getItem() instanceof ICurioItem)) {
return;
}
List<Component> list = event.getToolTip();
int markIdx1 = storedData.firstIndex();
int markIdx2 = storedData.secondIndex();
if (markIdx2 > list.size()) {
// Mod provided attribute list is not empty and differs from the curios gathered attribute list
markIdx1 = -1;
markIdx2 = -1;
}
if (markIdx1 != -1 && markIdx2 != -1) {
ListIterator<Component> it = list.listIterator(markIdx1);
for (int i = markIdx1; i < markIdx2 + 1; ++i) {
it.next();
it.remove();
}
if (AttributesLibClientAccess.shouldShowInTooltip(AttributesLibClientAccess.getHideFlags(stack), ItemStack.TooltipPart.MODIFIERS)) {
// TODO :: check if this causes duplicates if the slot can be equipped on an apotheosis-considered slot as well (e.g. armor slot)
AttributesLibClientAccess.applyModifierTooltips(event.getEntity(), stack, it::add, event.getFlags());
List<String> slots = storedData.slots();
if (slots != null) {
for (String slot : slots) {
Multimap<Attribute, AttributeModifier> unsorted = CuriosApi.getCuriosHelper().getAttributeModifiers(new SlotContext(slot, event.getEntity(), 0, false, true), UUID.randomUUID(), stack);
Multimap<Attribute, AttributeModifier> map = AttributeHelper.sortedMap();
List<Component> slotModifierTooltips = new ArrayList<>();
for (Map.Entry<Attribute, AttributeModifier> ent : unsorted.entries()) {
if (ent.getKey() != null && ent.getValue() != null) {
if (ent.getKey() instanceof CuriosHelper.SlotAttributeWrapper wrapper) {
// Curios slot modifiers are not part of the attribute registry
Component slotModifierTooltip = getSlotModifierTooltip(ent.getValue(), wrapper);
if (slotModifierTooltip != null) {
slotModifierTooltips.add(slotModifierTooltip);
}
continue;
}
map.put(ent.getKey(), ent.getValue());
} else {
AdventureModule.LOGGER.debug("Detected broken attribute modifier entry on item {}. Attr={}, Modif={}", stack, ent.getKey(), ent.getValue());
}
}
AttributesLibClientAccess.applyTextFor(event.getEntity(), stack, it::add, map, "ignore:" + slot, new HashSet<>(), event.getFlags());
for (Component slotModifier : slotModifierTooltips) {
it.add(slotModifier);
}
}
}
}
MinecraftForge.EVENT_BUS.post(new AddAttributeTooltipsEvent(stack, event.getEntity(), list, it, event.getFlags()));
}
}
private static Component getSlotModifierTooltip(final AttributeModifier modifier, CuriosHelper.SlotAttributeWrapper wrapper) {
Component component = null;
if (modifier.getAmount() > 0) {
component = Component.translatable(
"curios.modifiers.slots.plus." +
AttributeModifier.Operation.ADDITION.toValue(),
ATTRIBUTE_MODIFIER_FORMAT.format(modifier.getAmount()),
Component.translatable("curios.identifier." + wrapper.identifier))
.withStyle(ChatFormatting.BLUE);
} else if (modifier.getAmount() < 0) {
component = Component.translatable(
"curios.modifiers.slots.take." +
AttributeModifier.Operation.ADDITION.toValue(),
ATTRIBUTE_MODIFIER_FORMAT.format(modifier.getAmount()),
Component.translatable("curios.identifier." + wrapper.identifier))
.withStyle(ChatFormatting.RED);
}
return component;
}
}
package de.cadentem.trialanderror.mixin.apotheosis;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import shadows.apotheosis.core.attributeslib.client.AttributesLibClient;
@Mixin(value = AttributesLibClient.class, remap = false)
public class AttributesLibClientMixin {
@SuppressWarnings("unchecked")
@ModifyArg(method = "applyTextFor", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V", ordinal = 1))
private static <T> T trialanderror$addCuriosGroupTooltip(final T component, @Local(argsOnly = true) final String group) {
if (group.startsWith("ignore:")) {
return (T) Component.translatable("curios.modifiers." + group.substring(group.indexOf(":") + 1)).withStyle(ChatFormatting.GOLD);
}
return component;
}
}
package de.cadentem.trialanderror.mixin.apotheosis;
import com.google.common.collect.Multimap;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import shadows.apotheosis.core.attributeslib.client.AttributesLibClient;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
@Mixin(value = AttributesLibClient.class, remap = false)
public interface AttributesLibClientAccess {
@Invoker("getHideFlags")
static int getHideFlags(final ItemStack stack) {
throw new AssertionError();
}
@Invoker("shouldShowInTooltip")
static boolean shouldShowInTooltip(int hideFlags, final ItemStack.TooltipPart part) {
throw new AssertionError();
}
@Invoker("applyModifierTooltips")
static void applyModifierTooltips(@Nullable final Player player, final ItemStack stack, final Consumer<Component> tooltip, final TooltipFlag flag) {
throw new AssertionError();
}
@Invoker("applyTextFor")
static void applyTextFor(@Nullable final Player player, final ItemStack stack, final Consumer<Component> tooltip, final Multimap<Attribute, AttributeModifier> modifierMap, final String group, final Set<UUID> skips, final TooltipFlag flag) {
throw new AssertionError();
}
}
package de.cadentem.trialanderror.mixin.curios;
import com.llamalad7.mixinextras.sugar.Local;
import de.cadentem.trialanderror.core.mods.apotheosis.ApothCuriosTooltipHandler;
import net.minecraft.network.chat.Component;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import shadows.apotheosis.adventure.AdventureModule;
import top.theillusivec4.curios.client.ClientEventHandler;
import java.util.ArrayList;
import java.util.List;
/** Better tooltip handling for Apothic Curios */
@Mixin(value = ClientEventHandler.class, remap = false)
public class ClientEventHandlerMixin {
@Unique
private static final ThreadLocal<ApothCuriosTooltipHandler.StoredData> trialanderror$storedData = new ThreadLocal<>();
@Inject(method = "onTooltip", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/common/util/LazyOptional;ifPresent(Lnet/minecraftforge/common/util/NonNullConsumer;)V", ordinal = 1, shift = At.Shift.BEFORE))
private void trialanderror$storeData(final ItemTooltipEvent event, final CallbackInfo callback, @Local(name = "slots") final List<String> slots, @Local(name = "attributeTooltip") final List<Component> attributeTooltip) {
int currentIndex = event.getToolTip().size() - 1;
int nextIndex = currentIndex + attributeTooltip.size();
if (attributeTooltip.isEmpty() || nextIndex < currentIndex) {
currentIndex = -1;
nextIndex = -1;
}
// Some mods seem to clear the attribute list when receiving it
trialanderror$storedData.set(new ApothCuriosTooltipHandler.StoredData(event, slots, new ArrayList<>(attributeTooltip), currentIndex, nextIndex));
}
/** Some items don't return all attributes */
@ModifyVariable(method = "lambda$onTooltip$1", at = @At("STORE"), ordinal = 2)
private static List<Component> trialanderror$setActualAttributeList(final List<Component> tooltips) {
List<Component> attributeTooltip = trialanderror$storedData.get().attributeTooltip();
if (tooltips.isEmpty() && !attributeTooltip.isEmpty()) {
return attributeTooltip;
} else if (tooltips.size() != attributeTooltip.size()) {
// FIXME :: Only for dev (due to spam)
AdventureModule.LOGGER.debug("Attribute list sizes do not match:\n- Vanilla: [{}]\n- Mod: [{}]", attributeTooltip, tooltips);
}
return tooltips;
}
@Inject(method = "onTooltip", at = @At(value = "TAIL"))
private void trialanderror$handleTooltip(final ItemTooltipEvent event, final CallbackInfo callback) {
ApothCuriosTooltipHandler.StoredData storedData = trialanderror$storedData.get();
if (storedData != null) {
ApothCuriosTooltipHandler.handleTooltip(storedData);
}
trialanderror$storedData.remove();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment