package net.minecraft.util.datafix.fixes; import com.mojang.datafixers.DSL; import com.mojang.datafixers.DataFix; import com.mojang.datafixers.TypeRewriteRule; import com.mojang.datafixers.schemas.Schema; import com.mojang.datafixers.types.Type; import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Unit; import com.mojang.serialization.Dynamic; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.Locale; import java.util.Optional; import net.minecraft.Util; import net.minecraft.util.datafix.ExtraDataFixUtils; import org.jetbrains.annotations.Nullable; public class TextComponentHoverAndClickEventFix extends DataFix { public TextComponentHoverAndClickEventFix(Schema outputSchema) { super(outputSchema, true); } @Override protected TypeRewriteRule makeRule() { Type> type = (Type>)this.getInputSchema().getType(References.TEXT_COMPONENT).findFieldType("hoverEvent"); return this.createFixer(this.getInputSchema().getTypeRaw(References.TEXT_COMPONENT), this.getOutputSchema().getType(References.TEXT_COMPONENT), type); } private > TypeRewriteRule createFixer(Type inputComponentType, Type outputComponentType, Type hoverEventType) { Type>, Pair, Unit>, Pair, Pair, Dynamic>>>>>> type = DSL.named( References.TEXT_COMPONENT.typeName(), DSL.or( DSL.or(DSL.string(), DSL.list(inputComponentType)), DSL.and( DSL.optional(DSL.field("extra", DSL.list(inputComponentType))), DSL.optional(DSL.field("separator", inputComponentType)), DSL.optional(DSL.field("hoverEvent", hoverEventType)), DSL.remainderType() ) ) ); if (!type.equals(this.getInputSchema().getType(References.TEXT_COMPONENT))) { throw new IllegalStateException( "Text component type did not match, expected " + type + " but got " + this.getInputSchema().getType(References.TEXT_COMPONENT) ); } else { Type type2 = ExtraDataFixUtils.patchSubType(type, type, outputComponentType); return this.fixTypeEverywhere( "TextComponentHoverAndClickEventFix", type, outputComponentType, dynamicOps -> pair -> { boolean bl = ((Either)pair.getSecond()).map(either -> false, pairx -> { Pair, Dynamic> pair2 = (Pair, Dynamic>)((Pair)pairx.getSecond()).getSecond(); boolean blx = pair2.getFirst().left().isPresent(); boolean bl2 = pair2.getSecond().get("clickEvent").result().isPresent(); return blx || bl2; }); return !bl ? pair : Util.writeAndReadTypedOrThrow( ExtraDataFixUtils.cast(type2, pair, dynamicOps), outputComponentType, TextComponentHoverAndClickEventFix::fixTextComponent ) .getValue(); } ); } } private static Dynamic fixTextComponent(Dynamic data) { return data.renameAndFixField("hoverEvent", "hover_event", TextComponentHoverAndClickEventFix::fixHoverEvent) .renameAndFixField("clickEvent", "click_event", TextComponentHoverAndClickEventFix::fixClickEvent); } private static Dynamic copyFields(Dynamic newData, Dynamic oldData, String... fields) { for (String string : fields) { newData = Dynamic.copyField(oldData, string, newData, string); } return newData; } private static Dynamic fixHoverEvent(Dynamic data) { String string = data.get("action").asString(""); return switch (string) { case "show_text" -> data.renameField("contents", "value"); case "show_item" -> { Dynamic dynamic = data.get("contents").orElseEmptyMap(); Optional optional = dynamic.asString().result(); yield optional.isPresent() ? data.renameField("contents", "id") : copyFields(data.remove("contents"), dynamic, "id", "count", "components"); } case "show_entity" -> { Dynamic dynamic = data.get("contents").orElseEmptyMap(); yield copyFields(data.remove("contents"), dynamic, "id", "type", "name").renameField("id", "uuid").renameField("type", "id"); } default -> data; }; } @Nullable private static Dynamic fixClickEvent(Dynamic data) { String string = data.get("action").asString(""); String string2 = data.get("value").asString(""); return switch (string) { case "open_url" -> !validateUri(string2) ? null : data.renameField("value", "url"); case "open_file" -> data.renameField("value", "path"); case "run_command", "suggest_command" -> !validateChat(string2) ? null : data.renameField("value", "command"); case "change_page" -> { Integer integer = (Integer)data.get("value").result().map(TextComponentHoverAndClickEventFix::parseOldPage).orElse(null); if (integer == null) { yield null; } else { int i = Math.max(integer, 1); yield data.remove("value").set("page", data.createInt(i)); } } default -> data; }; } @Nullable private static Integer parseOldPage(Dynamic data) { Optional optional = data.asNumber().result(); if (optional.isPresent()) { return ((Number)optional.get()).intValue(); } else { try { return Integer.parseInt(data.asString("")); } catch (Exception var3) { return null; } } } private static boolean validateUri(String uri) { try { URI uRI = new URI(uri); String string = uRI.getScheme(); if (string == null) { return false; } else { String string2 = string.toLowerCase(Locale.ROOT); return "http".equals(string2) || "https".equals(string2); } } catch (URISyntaxException var4) { return false; } } private static boolean validateChat(String chat) { for (int i = 0; i < chat.length(); i++) { char c = chat.charAt(i); if (c == 167 || c < ' ' || c == 127) { return false; } } return true; } }