package net.minecraft.network.chat; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; import net.minecraft.core.Holder; import net.minecraft.core.UUIDUtil; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.nbt.TagParser; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.Nullable; public class HoverEvent { public static final Codec CODEC = Codec.withAlternative(HoverEvent.TypedHoverEvent.CODEC.codec(), HoverEvent.TypedHoverEvent.LEGACY_CODEC.codec()) .xmap(HoverEvent::new, hoverEvent -> hoverEvent.event); private final HoverEvent.TypedHoverEvent event; public HoverEvent(HoverEvent.Action action, T value) { this(new HoverEvent.TypedHoverEvent<>(action, value)); } private HoverEvent(HoverEvent.TypedHoverEvent event) { this.event = event; } /** * Gets the action to perform when this event is raised. */ public HoverEvent.Action getAction() { return this.event.action; } @Nullable public T getValue(HoverEvent.Action actionType) { return this.event.action == actionType ? actionType.cast(this.event.value) : null; } public boolean equals(Object object) { if (this == object) { return true; } else { return object != null && this.getClass() == object.getClass() ? ((HoverEvent)object).event.equals(this.event) : false; } } public String toString() { return this.event.toString(); } public int hashCode() { return this.event.hashCode(); } public static class Action implements StringRepresentable { public static final HoverEvent.Action SHOW_TEXT = new HoverEvent.Action<>( "show_text", true, ComponentSerialization.CODEC, (component, registryOps) -> DataResult.success(component) ); public static final HoverEvent.Action SHOW_ITEM = new HoverEvent.Action<>( "show_item", true, HoverEvent.ItemStackInfo.CODEC, HoverEvent.ItemStackInfo::legacyCreate ); public static final HoverEvent.Action SHOW_ENTITY = new HoverEvent.Action<>( "show_entity", true, HoverEvent.EntityTooltipInfo.CODEC, HoverEvent.EntityTooltipInfo::legacyCreate ); public static final Codec> UNSAFE_CODEC = StringRepresentable.fromValues( () -> new HoverEvent.Action[]{SHOW_TEXT, SHOW_ITEM, SHOW_ENTITY} ); public static final Codec> CODEC = UNSAFE_CODEC.validate(HoverEvent.Action::filterForSerialization); private final String name; private final boolean allowFromServer; final MapCodec> codec; final MapCodec> legacyCodec; public Action(String name, boolean allowFromServer, Codec codec, HoverEvent.LegacyConverter legacyConverter) { this.name = name; this.allowFromServer = allowFromServer; this.codec = codec.>xmap(object -> new HoverEvent.TypedHoverEvent<>(this, (T)object), typedHoverEvent -> typedHoverEvent.value) .fieldOf("contents"); this.legacyCodec = (new Codec>() { @Override public DataResult, D>> decode(DynamicOps dynamicOps, D object) { return ComponentSerialization.CODEC.decode(dynamicOps, object).flatMap(pair -> { DataResult dataResult; if (dynamicOps instanceof RegistryOps registryOps) { dataResult = legacyConverter.parse((Component)pair.getFirst(), registryOps); } else { dataResult = legacyConverter.parse((Component)pair.getFirst(), null); } return dataResult.map(objectx -> Pair.of(new HoverEvent.TypedHoverEvent<>(Action.this, objectx), pair.getSecond())); }); } public DataResult encode(HoverEvent.TypedHoverEvent typedHoverEvent, DynamicOps dynamicOps, D object) { return DataResult.error(() -> "Can't encode in legacy format"); } }).fieldOf("value"); } /** * Indicates whether this event can be run from chat text. */ public boolean isAllowedFromServer() { return this.allowFromServer; } @Override public String getSerializedName() { return this.name; } T cast(Object parameter) { return (T)parameter; } public String toString() { return ""; } private static DataResult> filterForSerialization(@Nullable HoverEvent.Action action) { if (action == null) { return DataResult.error(() -> "Unknown action"); } else { return !action.isAllowedFromServer() ? DataResult.error(() -> "Action not allowed: " + action) : DataResult.success(action, Lifecycle.stable()); } } } public static class EntityTooltipInfo { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( BuiltInRegistries.ENTITY_TYPE.byNameCodec().fieldOf("type").forGetter(entityTooltipInfo -> entityTooltipInfo.type), UUIDUtil.LENIENT_CODEC.fieldOf("id").forGetter(entityTooltipInfo -> entityTooltipInfo.id), ComponentSerialization.CODEC.lenientOptionalFieldOf("name").forGetter(entityTooltipInfo -> entityTooltipInfo.name) ) .apply(instance, HoverEvent.EntityTooltipInfo::new) ); public final EntityType type; public final UUID id; public final Optional name; @Nullable private List linesCache; public EntityTooltipInfo(EntityType type, UUID id, @Nullable Component name) { this(type, id, Optional.ofNullable(name)); } public EntityTooltipInfo(EntityType type, UUID id, Optional name) { this.type = type; this.id = id; this.name = name; } public static DataResult legacyCreate(Component name, @Nullable RegistryOps ops) { try { CompoundTag compoundTag = TagParser.parseTag(name.getString()); DynamicOps dynamicOps = (DynamicOps)(ops != null ? ops.withParent(JsonOps.INSTANCE) : JsonOps.INSTANCE); DataResult dataResult = ComponentSerialization.CODEC.parse(dynamicOps, JsonParser.parseString(compoundTag.getString("name"))); EntityType entityType = BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.parse(compoundTag.getString("type"))); UUID uUID = UUID.fromString(compoundTag.getString("id")); return dataResult.map(component -> new HoverEvent.EntityTooltipInfo(entityType, uUID, component)); } catch (Exception var7) { return DataResult.error(() -> "Failed to parse tooltip: " + var7.getMessage()); } } public List getTooltipLines() { if (this.linesCache == null) { this.linesCache = new ArrayList(); this.name.ifPresent(this.linesCache::add); this.linesCache.add(Component.translatable("gui.entity_tooltip.type", this.type.getDescription())); this.linesCache.add(Component.literal(this.id.toString())); } return this.linesCache; } public boolean equals(Object object) { if (this == object) { return true; } else if (object != null && this.getClass() == object.getClass()) { HoverEvent.EntityTooltipInfo entityTooltipInfo = (HoverEvent.EntityTooltipInfo)object; return this.type.equals(entityTooltipInfo.type) && this.id.equals(entityTooltipInfo.id) && this.name.equals(entityTooltipInfo.name); } else { return false; } } public int hashCode() { int i = this.type.hashCode(); i = 31 * i + this.id.hashCode(); return 31 * i + this.name.hashCode(); } } public static class ItemStackInfo { public static final Codec FULL_CODEC = ItemStack.CODEC.xmap(HoverEvent.ItemStackInfo::new, HoverEvent.ItemStackInfo::getItemStack); private static final Codec SIMPLE_CODEC = ItemStack.SIMPLE_ITEM_CODEC .xmap(HoverEvent.ItemStackInfo::new, HoverEvent.ItemStackInfo::getItemStack); public static final Codec CODEC = Codec.withAlternative(FULL_CODEC, SIMPLE_CODEC); private final Holder item; private final int count; private final DataComponentPatch components; @Nullable private ItemStack itemStack; ItemStackInfo(Holder item, int count, DataComponentPatch components) { this.item = item; this.count = count; this.components = components; } public ItemStackInfo(ItemStack stack) { this(stack.getItemHolder(), stack.getCount(), stack.getComponentsPatch()); } public boolean equals(Object object) { if (this == object) { return true; } else if (object != null && this.getClass() == object.getClass()) { HoverEvent.ItemStackInfo itemStackInfo = (HoverEvent.ItemStackInfo)object; return this.count == itemStackInfo.count && this.item.equals(itemStackInfo.item) && this.components.equals(itemStackInfo.components); } else { return false; } } public int hashCode() { int i = this.item.hashCode(); i = 31 * i + this.count; return 31 * i + this.components.hashCode(); } public ItemStack getItemStack() { if (this.itemStack == null) { this.itemStack = new ItemStack(this.item, this.count, this.components); } return this.itemStack; } private static DataResult legacyCreate(Component name, @Nullable RegistryOps ops) { try { CompoundTag compoundTag = TagParser.parseTag(name.getString()); DynamicOps dynamicOps = (DynamicOps)(ops != null ? ops.withParent(NbtOps.INSTANCE) : NbtOps.INSTANCE); return ItemStack.CODEC.parse(dynamicOps, compoundTag).map(HoverEvent.ItemStackInfo::new); } catch (CommandSyntaxException var4) { return DataResult.error(() -> "Failed to parse item tag: " + var4.getMessage()); } } } public interface LegacyConverter { DataResult parse(Component component, @Nullable RegistryOps registryOps); } record TypedHoverEvent(HoverEvent.Action action, T value) { public static final MapCodec> CODEC = HoverEvent.Action.CODEC .dispatchMap("action", HoverEvent.TypedHoverEvent::action, action -> action.codec); public static final MapCodec> LEGACY_CODEC = HoverEvent.Action.CODEC .dispatchMap("action", HoverEvent.TypedHoverEvent::action, action -> action.legacyCodec); } }