From b74d457eadf9f66ac246000376e6fbf4d2baa1c1 Mon Sep 17 00:00:00 2001 From: NatanLifshitz <142314885+NatanLifshitz@users.noreply.github.com> Date: Mon, 5 Feb 2024 21:32:03 +0200 Subject: [PATCH] 1.20.4 --- TESTING.md | 2 +- build.gradle | 4 +- common/build.gradle | 10 + .../config/MidnightConfig.java | 414 +++++++++--------- .../notenoughcrashes/gui/CrashScreen.java | 6 +- .../notenoughcrashes/gui/ProblemScreen.java | 11 +- .../mixinhandlers/InGameCatcher.java | 14 +- .../mixins/client/MixinKeyboard.java | 20 + .../mixins/client/MixinMinecraftClient.java | 59 ++- .../mixins/client/MixinMinecraftServer.java | 17 + .../utils/NecLocalization.java | 2 +- .../assets/notenoughcrashes/lang/en_us.json | 4 +- .../textures/gui/sprites/icon/reset.png | Bin 0 -> 178 bytes .../resources/notenoughcrashes.mixins.json | 1 + fabric/build.gradle | 4 +- .../fabric/platform/FabricPlatform.java | 14 +- gradle/libs.versions.toml | 23 +- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 3 +- 19 files changed, 340 insertions(+), 270 deletions(-) create mode 100644 common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinKeyboard.java create mode 100644 common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinMinecraftServer.java create mode 100644 common/src/main/resources/assets/notenoughcrashes/textures/gui/sprites/icon/reset.png diff --git a/TESTING.md b/TESTING.md index ef664f6..369bb20 100644 --- a/TESTING.md +++ b/TESTING.md @@ -39,7 +39,7 @@ To test in production, build the test Fabric/Forge mod and add them alongside NE ` - Verify: - The game crashes. - The crash screen appears. - - No mods are blamed. + - "Not Enough Crashes Test Mod" is blamed. - In the terminal, no information is repeated. - Verify in the **Log**, **TXT File**, and **Get Link Site**: - No information is repeated. diff --git a/build.gradle b/build.gradle index 0fb7988..57da941 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { alias libs.plugins.architectury.loom apply false - alias libs.plugins.loom.quiltflower apply false +// alias libs.plugins.loom.quiltflower apply false alias libs.plugins.minotaur apply false alias libs.plugins.architectury.plugin alias libs.plugins.cursegradle @@ -16,7 +16,7 @@ subprojects { apply plugin: 'maven-publish' apply plugin: 'com.matthewprenger.cursegradle' // apply plugin: "com.modrinth.minotaur" - apply plugin: "io.github.juuxel.loom-quiltflower" +// apply plugin: "io.github.juuxel.loom-quiltflower" dependencies { minecraft libs.minecraft diff --git a/common/build.gradle b/common/build.gradle index 7f73277..8070e72 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,7 +1,11 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' +} dependencies { // We depend on fabric loader here to use the fabric @Environment annotations // Do NOT use other classes from fabric loader modImplementation libs.fabric.loader + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } configurations { @@ -15,3 +19,9 @@ artifacts { architectury { common("fabric","forge") } +repositories { + mavenCentral() +} +kotlin { + jvmToolchain(17) +} diff --git a/common/src/main/java/fudge/notenoughcrashes/config/MidnightConfig.java b/common/src/main/java/fudge/notenoughcrashes/config/MidnightConfig.java index cdf8e42..a6816ca 100644 --- a/common/src/main/java/fudge/notenoughcrashes/config/MidnightConfig.java +++ b/common/src/main/java/fudge/notenoughcrashes/config/MidnightConfig.java @@ -1,9 +1,10 @@ package fudge.notenoughcrashes.config; - +import com.google.common.collect.Lists; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import fudge.notenoughcrashes.NotEnoughCrashes; import fudge.notenoughcrashes.platform.NecPlatform; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -13,18 +14,21 @@ import net.minecraft.client.gui.Element; import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.ClickableWidget; -import net.minecraft.client.gui.widget.ElementListWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.gui.tab.GridScreenTab; +import net.minecraft.client.gui.tab.Tab; +import net.minecraft.client.gui.tab.TabManager; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.*; +import net.minecraft.client.render.RenderLayer; import net.minecraft.client.resource.language.I18n; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.OrderedText; import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; -import java.awt.*; +import java.awt.Color; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,37 +37,32 @@ import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import java.util.*; +import java.util.List; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; -/** - * MidnightConfig v2.2.0 by TeamMidnightDust & Motschen - * Single class config library - feel free to copy! - *

- * Based on https://github.com/Minenash/TinyConfig - * Credits to Minenash - */ +/** MidnightConfig v2.5.1 by TeamMidnightDust & Motschen + * Single class config library - feel free to copy! + * Based on ... + * Credits to Minenash */ @SuppressWarnings("unchecked") public abstract class MidnightConfig { private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)"); - private static final Pattern DECIMAL_ONLY = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)"); + private static final Pattern DECIMAL_ONLY = Pattern.compile("-?(\\d+\\.?\\d*|\\d*\\.?\\d+|\\.)"); private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)"); private static final List entries = new ArrayList<>(); - protected static class EntryInfo { + public static class EntryInfo { Field field; Object widget; int width; - int max; boolean centered; - boolean comment = false; - Map.Entry error; + Text error; Object defaultValue; Object value; String tempValue; @@ -72,49 +71,39 @@ protected static class EntryInfo { Text name; int index; ClickableWidget colorButton; + Tab tab; } - public static final Map> configClass = new HashMap<>(); + public static final Map> configClass = new HashMap<>(); private static Path path; private static final Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE).addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy()).setPrettyPrinting().create(); - public static void init(String modid, Class config) { + public static void init(String modid, Class config) { path = NecPlatform.instance().getConfigDirectory().resolve(modid + ".json"); configClass.put(modid, config); for (Field field : config.getFields()) { EntryInfo info = new EntryInfo(); - if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class)) && !field.isAnnotationPresent(Server.class) && !field.isAnnotationPresent(Hidden.class)) - if (NecPlatform.instance().isClient()) initClient(modid, field, info); - if (field.isAnnotationPresent(Comment.class)){ - info.centered = field.getAnnotation(Comment.class).centered(); - info.comment = true; - } + if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class)) && !field.isAnnotationPresent(Server.class) && !field.isAnnotationPresent(Hidden.class) && NecPlatform.instance().isClient()) + initClient(modid, field, info); + if (field.isAnnotationPresent(Comment.class)) info.centered = field.getAnnotation(Comment.class).centered(); if (field.isAnnotationPresent(Entry.class)) try { info.defaultValue = field.get(null); - } catch (IllegalAccessException ignored) { - } catch (NullPointerException e) { - throw new IllegalArgumentException("Field " + field + " must be static to serve as a config field",e); - } - } - try { - gson.fromJson(Files.newBufferedReader(path), config); - } catch (Exception e) { - write(modid); + } catch (IllegalAccessException ignored) {} } + try { gson.fromJson(Files.newBufferedReader(path), config); } + catch (Exception e) { write(modid); } for (EntryInfo info : entries) { if (info.field.isAnnotationPresent(Entry.class)) try { info.value = info.field.get(null); info.tempValue = info.value.toString(); - } catch (IllegalAccessException ignored) { - } + } catch (IllegalAccessException ignored) {} } } - @Environment(EnvType.CLIENT) private static void initClient(String modid, Field field, EntryInfo info) { Class type = field.getType(); @@ -124,15 +113,12 @@ private static void initClient(String modid, Field field, EntryInfo info) { info.id = modid; if (e != null) { - if (!e.name().equals("")) info.name = Text.translatable(e.name()); + if (!e.name().isEmpty()) info.name = Text.translatable(e.name()); if (type == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true); - else if (type == float.class) - textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); + else if (type == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); else if (type == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false); - else if (type == String.class || type == List.class) { - info.max = e.max() == Double.MAX_VALUE ? Integer.MAX_VALUE : (int) e.max(); - textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true); - } else if (type == boolean.class) { + else if (type == String.class || type == List.class) textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true); + else if (type == boolean.class) { Function func = value -> Text.translatable((Boolean) value ? "gui.yes" : "gui.no").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED); info.widget = new AbstractMap.SimpleEntry>(button -> { info.value = !(Boolean) info.value; @@ -150,8 +136,12 @@ else if (type == String.class || type == List.class) { } entries.add(info); } + public static Tooltip getTooltip(EntryInfo info) { + String key = info.id + ".midnightconfig."+info.field.getName()+".tooltip"; + return Tooltip.of(info.error != null ? info.error : I18n.hasTranslation(key) ? Text.translatable(key) : Text.empty()); + } - private static void textField(EntryInfo info, Function f, Pattern pattern, double min, double max, boolean cast) { + private static void textField(EntryInfo info, Function f, Pattern pattern, double min, double max, boolean cast) { boolean isNumber = pattern != null; info.widget = (BiFunction>) (t, b) -> s -> { s = s.trim(); @@ -161,20 +151,21 @@ private static void textField(EntryInfo info, Function f, Patter boolean inLimits = false; info.error = null; if (!(isNumber && s.isEmpty()) && !s.equals("-") && !s.equals(".")) { - value = f.apply(s); + try { value = f.apply(s); } catch(NumberFormatException e){ return false; } inLimits = value.doubleValue() >= min && value.doubleValue() <= max; - info.error = inLimits ? null : new AbstractMap.SimpleEntry<>(t, Text.literal(value.doubleValue() < min ? - "§cMinimum " + (isNumber ? "value" : "length") + (cast ? " is " + (int) min : " is " + min) : - "§cMaximum " + (isNumber ? "value" : "length") + (cast ? " is " + (int) max : " is " + max))); + info.error = inLimits? null : Text.literal(value.doubleValue() < min ? + "§cMinimum " + (isNumber? "value" : "length") + (cast? " is " + (int)min : " is " + min) : + "§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max)).formatted(Formatting.RED); + t.setTooltip(getTooltip(info)); } info.tempValue = s; - t.setEditableColor(inLimits ? 0xFFFFFFFF : 0xFFFF7777); + t.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777); info.inLimits = inLimits; b.active = entries.stream().allMatch(e -> e.inLimits); if (inLimits && info.field.getType() != List.class) - info.value = isNumber ? value : s; + info.value = isNumber? value : s; else if (inLimits) { if (((List) info.value).size() == info.index) ((List) info.value).add(""); ((List) info.value).set(info.index, Arrays.stream(info.tempValue.replace("[", "").replace("]", "").split(", ")).toList().get(0)); @@ -185,28 +176,31 @@ else if (inLimits) { if (!HEXADECIMAL_ONLY.matcher(s).matches()) return false; try { info.colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); - } catch (Exception ignored) { - } + } catch (Exception ignored) {} } return true; }; } - + public static MidnightConfig getClass(String modid) { + try { return configClass.get(modid).getDeclaredConstructor().newInstance(); } catch (Exception e) {throw new RuntimeException(e);} + } public static void write(String modid) { + getClass(modid).writeChanges(modid); + } + + public void writeChanges(String modid) { path = NecPlatform.instance().getConfigDirectory().resolve(modid + ".json"); try { if (!Files.exists(path)) Files.createFile(path); - Files.write(path, gson.toJson(configClass.get(modid).getDeclaredConstructor().newInstance()).getBytes()); + Files.write(path, gson.toJson(getClass(modid)).getBytes()); } catch (Exception e) { - e.printStackTrace(); + e.fillInStackTrace(); } } - @Environment(EnvType.CLIENT) public static Screen getScreen(Screen parent, String modid) { return new MidnightConfigScreen(parent, modid); } - @Environment(EnvType.CLIENT) public static class MidnightConfigScreen extends Screen { protected MidnightConfigScreen(Screen parent, String modid) { @@ -214,27 +208,54 @@ protected MidnightConfigScreen(Screen parent, String modid) { this.parent = parent; this.modid = modid; this.translationPrefix = modid + ".midnightconfig."; + loadValues(); + + for (EntryInfo e : entries) { + if (e.id.equals(modid)) { + String tabId = e.field.isAnnotationPresent(Entry.class) ? e.field.getAnnotation(Entry.class).category() : e.field.getAnnotation(Comment.class).category(); + String name = translationPrefix + "category." + tabId; + if (!I18n.hasTranslation(name) && tabId.equals("default")) + name = translationPrefix + "title"; + if (!tabs.containsKey(name)) { + Tab tab = new GridScreenTab(Text.translatable(name)); + e.tab = tab; + tabs.put(name, tab); + } else e.tab = tabs.get(name); + } + } + tabNavigation = TabNavigationWidget.builder(tabManager, this.width).tabs(tabs.values().toArray(new Tab[0])).build(); + tabNavigation.selectTab(0, false); + tabNavigation.init(); + prevTab = tabManager.getCurrentTab(); } - public final String translationPrefix; public final Screen parent; public final String modid; public MidnightConfigListWidget list; public boolean reload = false; + public TabManager tabManager = new TabManager(a -> {}, a -> {}); + public Map tabs = new HashMap<>(); + public Tab prevTab; + public TabNavigationWidget tabNavigation; + public ButtonWidget done; + public double scrollProgress = 0d; // Real Time config update // @Override public void tick() { super.tick(); + if (prevTab != null && prevTab != tabManager.getCurrentTab()) { + prevTab = tabManager.getCurrentTab(); + this.list.clear(); + fillList(); + list.setScrollAmount(0); + } + scrollProgress = list.getScrollAmount(); for (EntryInfo info : entries) { - try { - info.field.set(null, info.value); - } catch (IllegalAccessException ignored) { - } + try {info.field.set(null, info.value);} catch (IllegalAccessException ignored) {} } updateResetButtons(); } - public void updateResetButtons() { if (this.list != null) { for (ButtonEntry entry : this.list.children()) { @@ -244,97 +265,109 @@ public void updateResetButtons() { } } } - public void loadValues() { - try { - gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); - } catch (Exception e) { - write(modid); - } + try { gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); } + catch (Exception e) { write(modid); } for (EntryInfo info : entries) { if (info.field.isAnnotationPresent(Entry.class)) try { info.value = info.field.get(null); info.tempValue = info.value.toString(); - } catch (IllegalAccessException ignored) { - } + } catch (IllegalAccessException ignored) {} } } - + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (this.tabNavigation.trySwitchTabsWithKey(keyCode)) return true; + return super.keyPressed(keyCode, scanCode, modifiers); + } @Override public void init() { super.init(); - if (!reload) loadValues(); - ButtonWidget done = ButtonWidget.builder(ScreenTexts.DONE, (button) -> { + tabNavigation.setWidth(this.width); + tabNavigation.init(); + if (tabs.size() > 1) this.addDrawableChild(tabNavigation); + + this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { + loadValues(); + Objects.requireNonNull(client).setScreen(parent); + }).dimensions(this.width / 2 - 154, this.height - 28, 150, 20).build()); + done = this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, (button) -> { for (EntryInfo info : entries) if (info.id.equals(modid)) { try { info.field.set(null, info.value); - } catch (IllegalAccessException ignored) { - } + } catch (IllegalAccessException ignored) {} } write(modid); Objects.requireNonNull(client).setScreen(parent); - }).dimensions(this.width / 2 + 4, this.height - 28, 150, 20).build(); + }).dimensions(this.width / 2 + 4, this.height - 28, 150, 20).build()); - this.list = new MidnightConfigListWidget(this.client, this.width, this.height, 32, this.height - 32, 25); + this.list = new MidnightConfigListWidget(this.client, this.width, this.height - 64, 32, 25); if (this.client != null && this.client.world != null) this.list.setRenderBackground(false); this.addSelectableChild(this.list); + + fillList(); + reload = true; + } + public void fillList() { for (EntryInfo info : entries) { - if (info.id.equals(modid)) { + if (info.id.equals(modid) && (info.tab == null || info.tab == tabManager.getCurrentTab())) { Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.field.getName())); - ButtonWidget resetButton = ButtonWidget.builder(Text.translatable("Reset").formatted(Formatting.RED), (button -> { + TextIconButtonWidget resetButton = TextIconButtonWidget.builder(Text.translatable("controls.reset"), (button -> { info.value = info.defaultValue; info.tempValue = info.defaultValue.toString(); info.index = 0; - double scrollAmount = list.getScrollAmount(); - this.reload = true; - Objects.requireNonNull(client).setScreen(this); - list.setScrollAmount(scrollAmount); - })).dimensions(width - 205, 0, 40, 20).build(); + list.clear(); + fillList(); + }), true).texture(new Identifier(NotEnoughCrashes.MOD_ID,"icon/reset"), 12, 12).dimension(40, 20).build(); + resetButton.setPosition(width - 205, 0); if (info.widget instanceof Map.Entry) { Map.Entry> widget = (Map.Entry>) info.widget; if (info.field.getType().isEnum()) widget.setValue(value -> Text.translatable(translationPrefix + "enum." + info.field.getType().getSimpleName() + "." + info.value.toString())); - this.list.addButton(List.of(ButtonWidget.builder(widget.getValue().apply(info.value), widget.getKey()).dimensions(width - 160, 0, 150, 20).build(), resetButton), name, info); + this.list.addButton(List.of(ButtonWidget.builder(widget.getValue().apply(info.value), widget.getKey()).dimensions(width - 160, 0, 150, 20).tooltip(getTooltip(info)).build(), resetButton), name, info); } else if (info.field.getType() == List.class) { if (!reload) info.index = 0; - TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null); + TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, Text.empty()); widget.setMaxLength(info.width); if (info.index < ((List) info.value).size()) widget.setText((String.valueOf(((List) info.value).get(info.index)))); - else widget.setText(""); Predicate processor = ((BiFunction>) info.widget).apply(widget, done); widget.setTextPredicate(processor); resetButton.setWidth(20); - resetButton.setMessage(Text.literal("R").formatted(Formatting.RED)); ButtonWidget cycleButton = ButtonWidget.builder(Text.literal(String.valueOf(info.index)).formatted(Formatting.GOLD), (button -> { - ((List) info.value).remove(""); - double scrollAmount = list.getScrollAmount(); - this.reload = true; + if (((List) info.value).contains("")) ((List) info.value).remove(""); info.index = info.index + 1; if (info.index > ((List) info.value).size()) info.index = 0; - Objects.requireNonNull(client).setScreen(this); - list.setScrollAmount(scrollAmount); + list.clear(); + fillList(); })).dimensions(width - 185, 0, 20, 20).build(); + widget.setTooltip(getTooltip(info)); this.list.addButton(List.of(widget, resetButton, cycleButton), name, info); } else if (info.widget != null) { - TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null); - widget.setMaxLength(info.width); - widget.setText(info.tempValue); - Predicate processor = ((BiFunction>) info.widget).apply(widget, done); - widget.setTextPredicate(processor); - if (info.field.getAnnotation(Entry.class).isColor()) { + ClickableWidget widget; + Entry e = info.field.getAnnotation(Entry.class); + if (e.isSlider()) + widget = new MidnightSliderWidget(width - 160, 0, 150, 20, Text.of(info.tempValue), (Double.parseDouble(info.tempValue) - e.min()) / (e.max() - e.min()), info); + else + widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null, Text.of(info.tempValue)); + if (widget instanceof TextFieldWidget textField) { + textField.setMaxLength(info.width); + textField.setText(info.tempValue); + Predicate processor = ((BiFunction>) info.widget).apply(textField, done); + textField.setTextPredicate(processor); + } + widget.setTooltip(getTooltip(info)); + if (e.isColor()) { resetButton.setWidth(20); - resetButton.setMessage(Text.literal("R").formatted(Formatting.RED)); ButtonWidget colorButton = ButtonWidget.builder(Text.literal("⬛"), (button -> { })).dimensions(width - 185, 0, 20, 20).build(); try { colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); - } catch (Exception ignored) { - } + } catch (Exception ignored) {} info.colorButton = colorButton; colorButton.active = false; this.list.addButton(List.of(widget, resetButton, colorButton), name, info); @@ -343,164 +376,115 @@ public void init() { this.list.addButton(List.of(), name, info); } } + list.setScrollAmount(scrollProgress); updateResetButtons(); } - - this.addDrawableChild(ButtonWidget.builder(ScreenTexts.CANCEL, button -> { - loadValues(); - Objects.requireNonNull(client).setScreen(parent); - }).dimensions(this.width / 2 - 154, this.height - 28, 150, 20).build()); - - this.addDrawableChild(done); - } - @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - this.renderBackground(context, mouseX, mouseY, delta); + if (client != null && client.world != null) super.renderInGameBackground(context); this.list.render(context, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 15, 0xFFFFFF); - - for (EntryInfo info : entries) { - if (info.id.equals(modid)) { - if (list.getHoveredButton(mouseX, mouseY).isPresent()) { - ClickableWidget buttonWidget = list.getHoveredButton(mouseX, mouseY).get(); - Text text = ButtonEntry.buttonsWithText.get(buttonWidget); - Text name = Text.translatable(this.translationPrefix + info.field.getName()); - String key = translationPrefix + info.field.getName() + ".tooltip"; + super.render(context,mouseX,mouseY,delta); - if (info.error != null && text.equals(name)) - context.drawTooltip(textRenderer, info.error.getValue(), mouseX, mouseY); - else if (I18n.hasTranslation(key) && text.equals(name)) { - List list = new ArrayList<>(); - for (String str : I18n.translate(key).split("\n")) - list.add(Text.literal(str)); - context.drawTooltip(textRenderer, list, mouseX, mouseY); - } - } - } - } - super.render(context, mouseX, mouseY, delta); + if (tabs.size() < 2) context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 15, 0xFFFFFF); } + @Override public void renderBackground(DrawContext c, int x, int y, float d) {} } - @Environment(EnvType.CLIENT) public static class MidnightConfigListWidget extends ElementListWidget { - TextRenderer textRenderer; - - public MidnightConfigListWidget(MinecraftClient minecraftClient, int i, int j, int k, int l, int m) { - super(minecraftClient, i, j, k, l, m); - this.centerListVertically = false; - textRenderer = minecraftClient.textRenderer; + public MidnightConfigListWidget(MinecraftClient client, int width, int height, int y, int itemHeight) { + super(client, width, height, y, itemHeight); } - @Override - public int getScrollbarPositionX() { - return this.width - 7; - } + public int getScrollbarPositionX() { return this.width -7; } public void addButton(List buttons, Text text, EntryInfo info) { - this.addEntry(ButtonEntry.create(buttons, text, info)); + this.addEntry(new ButtonEntry(buttons, text, info)); } - + public void clear() { this.clearEntries(); } @Override - public int getRowWidth() { - return 10000; - } - - public Optional getHoveredButton(double mouseX, double mouseY) { - for (ButtonEntry buttonEntry : this.children()) { - if (!buttonEntry.buttons.isEmpty() && buttonEntry.buttons.get(0).isMouseOver(mouseX, mouseY)) { - return Optional.of(buttonEntry.buttons.get(0)); - } - } - return Optional.empty(); + public int getRowWidth() { return 10000; } + @Override + protected void renderDecorations(DrawContext c, int mouseX, int mouseY) { + c.setShaderColor(0.25F, 0.25F, 0.25F, 1.0F); + c.drawTexture(Screen.OPTIONS_BACKGROUND_TEXTURE, this.getX(), 0, 0.0F, 0.0F, this.width, this.getY(), 32, 32); + c.drawTexture(Screen.OPTIONS_BACKGROUND_TEXTURE, this.getX(), this.getBottom(), 0.0F, 0.0F, this.width, this.height, 32, 32); + c.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + if (client == null || client.world == null) return; + c.fillGradient(RenderLayer.getGuiOverlay(), this.getX(), this.getY(), this.getRight(), this.getY() + 4, -16777216, 0, 0); + c.fillGradient(RenderLayer.getGuiOverlay(), this.getX(), this.getBottom() - 4, this.getRight(), this.getBottom(), 0, -16777216, 0); } } - public static class ButtonEntry extends ElementListWidget.Entry { private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; public final List buttons; private final Text text; public final EntryInfo info; - private final List children = new ArrayList<>(); public static final Map buttonsWithText = new HashMap<>(); private ButtonEntry(List buttons, Text text, EntryInfo info) { - if (!buttons.isEmpty()) buttonsWithText.put(buttons.get(0), text); + if (!buttons.isEmpty()) buttonsWithText.put(buttons.get(0),text); this.buttons = buttons; this.text = text; this.info = info; - children.addAll(buttons); - } - - public static ButtonEntry create(List buttons, Text text, EntryInfo info) { - return new ButtonEntry(buttons, text, info); } - - @Override public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - buttons.forEach(b -> { - b.setY(y); - b.render(context, mouseX, mouseY, tickDelta); - }); - var color = info.comment ? 0x808080 : 0xFFFFFF; + buttons.forEach(b -> { b.setY(y); b.render(context, mouseX, mouseY, tickDelta); }); if (text != null && (!text.getString().contains("spacer") || !buttons.isEmpty())) { - if (info.centered) - context.drawTextWithShadow(textRenderer, text, MinecraftClient.getInstance().getWindow().getScaledWidth() / 2 - (textRenderer.getWidth(text) / 2), y + 5, color); - else context.drawTextWithShadow(textRenderer, text, 12, y + 5, color); + if (info.centered) context.drawTextWithShadow(textRenderer, text, MinecraftClient.getInstance().getWindow().getScaledWidth() / 2 - (textRenderer.getWidth(text) / 2), y + 5, 0xFFFFFF); + else { + int wrappedY = y; + for(Iterator iterator = textRenderer.wrapLines(text, (buttons.size() > 1 ? buttons.get(1).getX()-24 : MinecraftClient.getInstance().getWindow().getScaledWidth() - 24)).iterator(); iterator.hasNext(); wrappedY += 9) { + OrderedText orderedText = iterator.next(); + context.drawTextWithShadow(textRenderer, orderedText, 12, wrappedY + 5, 0xFFFFFF); + } + } } } + public List children() {return Lists.newArrayList(buttons);} + public List selectableChildren() {return Lists.newArrayList(buttons);} + } + public static class MidnightSliderWidget extends SliderWidget { + private final EntryInfo info; private final Entry e; + public MidnightSliderWidget(int x, int y, int width, int height, Text text, double value, EntryInfo info) { + super(x, y, width, height, text, value); + this.e = info.field.getAnnotation(Entry.class); + this.info = info; + } - public List children() { - return children; + @Override + protected void updateMessage() { + this.setMessage(Text.of(info.tempValue)); } - public List selectableChildren() { - return children; + @Override + protected void applyValue() { + if (info.field.getType() == int.class) info.value = ((Number) (e.min() + value * (e.max() - e.min()))).intValue(); + else if (info.field.getType() == double.class) info.value = Math.round((e.min() + value * (e.max() - e.min())) * (double) e.precision()) / (double) e.precision(); + else if (info.field.getType() == float.class) info.value = Math.round((e.min() + value * (e.max() - e.min())) * (float) e.precision()) / (float) e.precision(); + info.tempValue = String.valueOf(info.value); } } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface Entry { + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Entry { int width() default 100; - double min() default Double.MIN_NORMAL; - double max() default Double.MAX_VALUE; - String name() default ""; - boolean isColor() default false; + boolean isSlider() default false; + int precision() default 100; + String category() default "default"; } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface Client { - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface Server { - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface Hidden { - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.FIELD) - public @interface Comment { + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Client {} + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Server {} + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Hidden {} + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Comment { boolean centered() default false; + String category() default "default"; } public static class HiddenAnnotationExclusionStrategy implements ExclusionStrategy { - public boolean shouldSkipClass(Class clazz) { - return false; - } - + public boolean shouldSkipClass(Class clazz) { return false; } public boolean shouldSkipField(FieldAttributes fieldAttributes) { return fieldAttributes.getAnnotation(Entry.class) == null; } diff --git a/common/src/main/java/fudge/notenoughcrashes/gui/CrashScreen.java b/common/src/main/java/fudge/notenoughcrashes/gui/CrashScreen.java index 7b63a73..5311639 100644 --- a/common/src/main/java/fudge/notenoughcrashes/gui/CrashScreen.java +++ b/common/src/main/java/fudge/notenoughcrashes/gui/CrashScreen.java @@ -1,6 +1,7 @@ package fudge.notenoughcrashes.gui; import fudge.notenoughcrashes.config.NecConfig; +import fudge.notenoughcrashes.mixinhandlers.InGameCatcher; import fudge.notenoughcrashes.utils.NecLocalization; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -27,7 +28,10 @@ public void init() { super.init(); ButtonWidget mainMenuButton = ButtonWidget.builder( NecLocalization.translatedText("gui.toTitle"), - button -> MinecraftClient.getInstance().setScreen(new TitleScreen()) + button -> { + InGameCatcher.crashScreenActive = true; + MinecraftClient.getInstance().setScreen(new TitleScreen()); + } ) .dimensions(width / 2 - 155, height / 4 + 120 + 12, 150, 20) .build(); diff --git a/common/src/main/java/fudge/notenoughcrashes/gui/ProblemScreen.java b/common/src/main/java/fudge/notenoughcrashes/gui/ProblemScreen.java index dbbc580..7bcb81f 100644 --- a/common/src/main/java/fudge/notenoughcrashes/gui/ProblemScreen.java +++ b/common/src/main/java/fudge/notenoughcrashes/gui/ProblemScreen.java @@ -94,13 +94,7 @@ private void handleLegacyLinkClick(ButtonWidget buttonWidget) { if (uploadedCrashLink == null) { uploadedCrashLink = LegacyCrashLogUpload.upload(report.asString()); } - MinecraftClient.getInstance().setScreen(new ConfirmLinkScreen(b -> { - if (b) { - Util.getOperatingSystem().open(uploadedCrashLink); - } - - MinecraftClient.getInstance().setScreen(construct(report)); - }, uploadedCrashLink, true)); + Util.getOperatingSystem().open(uploadedCrashLink); } catch (Throwable e) { NotEnoughCrashes.getLogger().error("Exception when crash menu button clicked:", e); buttonWidget.setMessage(NecLocalization.translatedText("notenoughcrashes.gui.failed")); @@ -139,7 +133,8 @@ public void init() { addDrawableChild( ButtonWidget.builder( NecLocalization.translatedText("notenoughcrashes.gui.getLink") - .copy().setStyle(Style.EMPTY.withColor(GRAY)) + // No longer grayed out because Crashy is not working ATM +// .copy().setStyle(Style.EMPTY.withColor(GRAY)) , this::handleLegacyLinkClick) .dimensions(width / 2 - 155 + 160, height / 4 + 144 + 12, 150, 20) .build() diff --git a/common/src/main/java/fudge/notenoughcrashes/mixinhandlers/InGameCatcher.java b/common/src/main/java/fudge/notenoughcrashes/mixinhandlers/InGameCatcher.java index 78f82fd..e49a9f2 100644 --- a/common/src/main/java/fudge/notenoughcrashes/mixinhandlers/InGameCatcher.java +++ b/common/src/main/java/fudge/notenoughcrashes/mixinhandlers/InGameCatcher.java @@ -1,8 +1,8 @@ package fudge.notenoughcrashes.mixinhandlers; -import fudge.notenoughcrashes.config.NecConfig; import fudge.notenoughcrashes.NotEnoughCrashes; import fudge.notenoughcrashes.StateManager; +import fudge.notenoughcrashes.config.NecConfig; import fudge.notenoughcrashes.gui.CrashScreen; import fudge.notenoughcrashes.patches.MinecraftClientAccess; import fudge.notenoughcrashes.stacktrace.CrashUtils; @@ -24,14 +24,15 @@ public class InGameCatcher { private static int clientCrashCount = 0; private static int serverCrashCount = 0; + public static boolean crashScreenActive = false; public static void handleClientCrash(CrashReport report) { clientCrashCount++; addInfoToCrash(report); resetStates(); - boolean reported = report.getCause() instanceof CrashException; - LOGGER.fatal(reported ? "Reported" : "Unreported" + " exception thrown!", report.getCause()); +// boolean reported = report.getCause() instanceof CrashException; +// LOGGER.fatal(reported ? "Reported" : "Unreported" + " exception thrown!", report.getCause()); displayCrashScreen(report, clientCrashCount, true); // Continue game loop getClient().run(); @@ -66,6 +67,9 @@ private static void resetCriticalGameState() { } client.player = null; client.world = null; + + var server = client.getServer(); + if (server != null) server.stop(true); } private static void resetModState() { @@ -90,6 +94,7 @@ public static void addInfoToCrash(CrashReport report) { } private static void displayCrashScreen(CrashReport report, int crashCount, boolean clientCrash) { + crashScreenActive = true; try { if (EntryPointCatcher.crashedDuringStartup()) { throw new IllegalStateException("Could not initialize startup crash screen"); @@ -107,9 +112,10 @@ private static void displayCrashScreen(CrashReport report, int crashCount, boole // Display the crash screen getClient().setScreen(new CrashScreen(report)); } catch (Throwable t) { + crashScreenActive = false; // The crash screen has crashed. Report it normally instead. LOGGER.error("An uncaught exception occured while displaying the crash screen, making normal report instead", t); - MinecraftClient.printCrashReport(report); + getClient().printCrashReport(report); System.exit(report.getFile() != null ? -1 : -2); } } diff --git a/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinKeyboard.java b/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinKeyboard.java new file mode 100644 index 0000000..5b055c9 --- /dev/null +++ b/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinKeyboard.java @@ -0,0 +1,20 @@ +package fudge.notenoughcrashes.mixins.client; + +import fudge.notenoughcrashes.mixinhandlers.InGameCatcher; +import net.minecraft.client.Keyboard; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Keyboard.class) +public class MixinKeyboard { + /** + * pollDebugCrash() keeps crashing the game when we display the crash screen, infinitely times over, + * so we need to stop it from crashing after it has done its job just once. + */ + @Inject(method = "pollDebugCrash()V", at = @At("HEAD"), cancellable = true) + public void pollDebugCrashDontCrashInfinitely(CallbackInfo ci) { + if (InGameCatcher.crashScreenActive) ci.cancel(); + } +} \ No newline at end of file diff --git a/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinMinecraftClient.java b/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinMinecraftClient.java index b9de82a..929c018 100644 --- a/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinMinecraftClient.java +++ b/common/src/main/java/fudge/notenoughcrashes/mixins/client/MixinMinecraftClient.java @@ -1,10 +1,12 @@ package fudge.notenoughcrashes.mixins.client; import fudge.notenoughcrashes.NotEnoughCrashes; +import fudge.notenoughcrashes.gui.InitErrorScreen; import fudge.notenoughcrashes.mixinhandlers.EntryPointCatcher; import fudge.notenoughcrashes.mixinhandlers.InGameCatcher; import fudge.notenoughcrashes.patches.MinecraftClientAccess; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.util.crash.CrashReport; import net.minecraft.util.profiler.Recorder; import net.minecraft.util.thread.ReentrantThreadExecutor; @@ -20,6 +22,7 @@ import java.util.Queue; import java.util.function.Supplier; +//TODO: infinite repeating crashes in different cases, need to go over the crash cases and solve them one by one. @Mixin(MinecraftClient.class) @@ -36,7 +39,7 @@ public abstract class MixinMinecraftClient extends ReentrantThreadExecutor@HY@>Bm7Xq+Ar_~v25sa$V8G$MD(Gb0)$Z=`DoID8cr-L|2lgJYfne@Ijm!9t=s0