diff --git a/src/main/java/gregtech/api/gui/translation/EnhancedTextComponentSerializer.java b/src/main/java/gregtech/api/gui/translation/EnhancedTextComponentSerializer.java new file mode 100755 index 0000000000..539e71084c --- /dev/null +++ b/src/main/java/gregtech/api/gui/translation/EnhancedTextComponentSerializer.java @@ -0,0 +1,74 @@ +package gregtech.api.gui.translation; + +import java.lang.reflect.Type; + +import javax.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +import net.minecraft.util.EnumTypeAdapterFactory; +import net.minecraft.util.JsonUtils; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.Style; +import net.minecraft.util.text.TextComponentTranslation; + +public class EnhancedTextComponentSerializer extends ITextComponent.Serializer { + + private static final Gson GSON; + + @Override + public ITextComponent deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final ITextComponent superResult = super.deserialize(element, type, context); + if (!(superResult instanceof TextComponentTranslation)) { + return superResult; + } + final TextComponentTranslation original = (TextComponentTranslation) superResult; + final EnhancedTextComponentTranslation result = new EnhancedTextComponentTranslation(original.getKey(), original.getFormatArgs()); + for (ITextComponent sibling : original.getSiblings()) { + result.appendSibling(sibling); + } + return result; + } + + /** + * Serializes a component into JSON. + */ + public static String componentToJson(final ITextComponent component) { + return GSON.toJson(component); + } + + /** + * Parses a JSON string into a {@link ITextComponent}, with strict parsing. + * + * @see #fromJsonLenient(String) + * @see {@link com.google.gson.stream.JsonReader#setLenient(boolean)} + */ + @Nullable + public static ITextComponent jsonToComponent(final String json) { + return JsonUtils.gsonDeserialize(GSON, json, ITextComponent.class, false); + } + + /** + * Parses a JSON string into a {@link ITextComponent}, being lenient upon parse + * errors. + * + * @see #jsonToComponent(String) + * @see {@link com.google.gson.stream.JsonReader#setLenient(boolean)} + */ + @Nullable + public static ITextComponent fromJsonLenient(final String json) { + return JsonUtils.gsonDeserialize(GSON, json, ITextComponent.class, true); + } + + static { + GsonBuilder gsonbuilder = new GsonBuilder(); + gsonbuilder.registerTypeHierarchyAdapter(ITextComponent.class, new EnhancedTextComponentSerializer()); + gsonbuilder.registerTypeHierarchyAdapter(Style.class, new Style.Serializer()); + gsonbuilder.registerTypeAdapterFactory(new EnumTypeAdapterFactory()); + GSON = gsonbuilder.create(); + } +} diff --git a/src/main/java/gregtech/api/gui/translation/EnhancedTextComponentTranslation.java b/src/main/java/gregtech/api/gui/translation/EnhancedTextComponentTranslation.java new file mode 100755 index 0000000000..3e9b3b3c99 --- /dev/null +++ b/src/main/java/gregtech/api/gui/translation/EnhancedTextComponentTranslation.java @@ -0,0 +1,135 @@ +package gregtech.api.gui.translation; + +import java.util.IllegalFormatException; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import net.minecraft.util.text.TextComponentTranslationFormatException; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; + +public class EnhancedTextComponentTranslation extends TextComponentTranslation { + + public static final Pattern ENHANCED_STRING_VARIABLE_PATTERN = Pattern.compile("%(\\\\d+\\\\$)?([-#+ 0,(\\\\<]*)?(\\\\d+)?(\\\\.\\\\d+)?([tT])?([a-zA-Z%])"); + + private final List copyChildren; + + public EnhancedTextComponentTranslation(final String translationKey, final Object... args) { + super(translationKey, args); + this.copyChildren = ObfuscationReflectionHelper.getPrivateValue(TextComponentTranslation.class, this, "children"); + } + + @Override + protected void initializeFromFormat(final String format) { + final Matcher matcher = ENHANCED_STRING_VARIABLE_PATTERN.matcher(format); + int argIndex = 0; + int start = 0; + Object[] formatArgs = getFormatArgs(); + + try { + int end; + + for (; matcher.find(start); start = end) { + final int found = matcher.start(); + end = matcher.end(); + + // Text before the first % + if (found > start) { + final TextComponentString text = new TextComponentString(String.format(format.substring(start, found))); + text.getStyle().setParentStyle(this.getStyle()); + this.copyChildren.add(text); + } + + final String formatOption = matcher.group(6); + final String string = format.substring(found, end); + + // Escape sequence %% + if ("%".equals(formatOption) && "%%".equals(string)) { + final TextComponentString text = new TextComponentString("%"); + text.getStyle().setParentStyle(this.getStyle()); + this.copyChildren.add(text); + } else { + // Argument number specifier + final String arg = matcher.group(1); + final int index = arg != null ? Integer.parseInt(arg) - 1 : argIndex++; + + if (index < formatArgs.length) { + final Object object = formatArgs[index]; + final ITextComponent text; + if (object == null) { + text = new TextComponentString("null"); + } + else if (object instanceof ITextComponent) { + text = (ITextComponent) object; + } else { + // Concatenate all the format options except the index specifier + final StringBuilder builder = new StringBuilder(); + builder.append('%'); + // groupCount doesn't include group 0 hence <= + for (int g = 2; g <= matcher.groupCount(); ++g) { + final String group = matcher.group(g); + if (group != null) { + builder.append(group); + } + } + text = new TextComponentString(String.format(builder.toString(), fixArg(formatOption, object))); + } + text.getStyle().setParentStyle(this.getStyle()); + this.copyChildren.add(text); + } + } + } + + // Trailing text + if (start < format.length()) { + final TextComponentString text = new TextComponentString(String.format(format.substring(start))); + text.getStyle().setParentStyle(this.getStyle()); + this.copyChildren.add(text); + } + } catch (IllegalFormatException | NumberFormatException e) { + throw new TextComponentTranslationFormatException(this, e); + } + } + + private static Object fixArg(final String formatOption, final Object original) { + if (original instanceof String) { + final String string = (String) original; + // Short circuit common case + if ("s".equals(formatOption)) { + return string; + } + if ("d".equals(formatOption) || "o".equals(formatOption) || "x".equalsIgnoreCase(formatOption)) { + return Long.valueOf(string); + } + if ("e".equalsIgnoreCase(formatOption) || "f".equals(formatOption) || "g".equalsIgnoreCase(formatOption) || "a".equalsIgnoreCase(formatOption)) { + return Double.valueOf(string); + } + } + return original; + } + + @Override + public EnhancedTextComponentTranslation createCopy() { + final Object[] formatArgs = getFormatArgs(); + final Object[] copyArgs = new Object[formatArgs.length]; + + for (int i = 0; i < formatArgs.length; ++i) { + if (formatArgs[i] instanceof ITextComponent) { + copyArgs[i] = ((ITextComponent) formatArgs[i]).createCopy(); + } else { + copyArgs[i] = formatArgs[i]; + } + } + + final EnhancedTextComponentTranslation copy = new EnhancedTextComponentTranslation(getKey(), copyArgs); + copy.setStyle(this.getStyle().createShallowCopy()); + + for (ITextComponent sibling : this.getSiblings()) { + copy.appendSibling(sibling.createCopy()); + } + return copy; + } +} diff --git a/src/main/java/gregtech/api/gui/widgets/AdvancedTextWidget.java b/src/main/java/gregtech/api/gui/widgets/AdvancedTextWidget.java index 5dbd6a25b9..c3a4f6697f 100644 --- a/src/main/java/gregtech/api/gui/widgets/AdvancedTextWidget.java +++ b/src/main/java/gregtech/api/gui/widgets/AdvancedTextWidget.java @@ -2,6 +2,8 @@ import gregtech.api.gui.IRenderContext; import gregtech.api.gui.Widget; +import gregtech.api.gui.translation.EnhancedTextComponentSerializer; +import gregtech.api.gui.translation.EnhancedTextComponentTranslation; import gregtech.api.util.Position; import gregtech.api.util.Size; import net.minecraft.client.Minecraft; @@ -113,7 +115,7 @@ public void detectAndSendChanges() { writeUpdateInfo(1, buffer -> { buffer.writeVarInt(displayText.size()); for (ITextComponent textComponent : displayText) { - buffer.writeString(ITextComponent.Serializer.componentToJson(textComponent)); + buffer.writeString(EnhancedTextComponentSerializer.componentToJson(textComponent)); } }); } @@ -169,7 +171,7 @@ public void readUpdateInfo(int id, PacketBuffer buffer) { int count = buffer.readVarInt(); for (int i = 0; i < count; i++) { String jsonText = buffer.readString(32767); - this.displayText.add(ITextComponent.Serializer.jsonToComponent(jsonText)); + this.displayText.add(EnhancedTextComponentSerializer.jsonToComponent(jsonText)); } formatDisplayText(); updateComponentTextSize(); diff --git a/src/main/resources/assets/gregtech/lang/en_us.lang b/src/main/resources/assets/gregtech/lang/en_us.lang index 411412f710..ba19d13b81 100755 --- a/src/main/resources/assets/gregtech/lang/en_us.lang +++ b/src/main/resources/assets/gregtech/lang/en_us.lang @@ -2961,7 +2961,7 @@ gregtech.multiblock.invalid_structure=Invalid structure. gregtech.multiblock.invalid_structure.tooltip=This block is a controller of the multiblock structure. For building help, see structure template in JEI gregtech.multiblock.validation_failed=Invalid amount of inputs/outputs. gregtech.multiblock.max_energy_per_tick=Max EU/t: %s (%s) -gregtech.multiblock.generation_eu=Outputting: %s EU/t +gregtech.multiblock.generation_eu=Outputting: %,d EU/t gregtech.multiblock.preview.tilt=Shift+LMB to tilt gregtech.multiblock.preview.zoom=Shift+RMB to zoom gregtech.multiblock.preview.pan=LMB+Drag to pan