diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index b600be89..c1993f1b 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -22,7 +22,6 @@ import net.minecraft.client.gui.widget.ElementListWidget; import net.minecraft.client.gui.widget.EntryListWidget; import net.minecraft.client.render.*; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.OrderedText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; @@ -207,7 +206,8 @@ public void renderList(DrawContext DrawContext, int mouseX, int mouseY, float de children().add(new MojangCreditsEntry(line)); } } else if (!"java".equals(mod.getId())) { - List credits = mod.getCredits(); + var credits = mod.getCredits(); + if (!credits.isEmpty()) { children().add(emptyEntry); @@ -215,12 +215,31 @@ public void renderList(DrawContext DrawContext, int mouseX, int mouseY, float de children().add(new DescriptionEntry(line)); } - for (String credit : credits) { + var iterator = credits.entrySet().iterator(); + + while (iterator.hasNext()) { int indent = 8; - for (OrderedText line : textRenderer.wrapLines(Text.literal(credit), wrapWidth - 16)) { + + var role = iterator.next(); + var roleName = role.getKey(); + + for (var line : textRenderer.wrapLines(this.creditsRoleText(roleName), wrapWidth - 16)) { children().add(new DescriptionEntry(line, indent)); indent = 16; } + + for (var contributor : role.getValue()) { + indent = 16; + + for (var line : textRenderer.wrapLines(Text.literal(contributor), wrapWidth - 24)) { + children().add(new DescriptionEntry(line, indent)); + indent = 24; + } + } + + if (iterator.hasNext()) { + children().add(emptyEntry); + } } } } @@ -331,6 +350,18 @@ public void renderScrollBar(BufferBuilder bufferBuilder, Tessellator tessellator } } + private Text creditsRoleText(String roleName) { + // Replace spaces and dashes in role names with underscores if they exist + // Notably Quilted Fabric API does this with FabricMC as "Upstream Owner" + var translationKey = roleName.replaceAll("[\s-]", "_"); + + // Add an s to the default untranslated string if it ends in r since this + // Fixes common role names people use in English (e.g. Author -> Authors) + var fallback = roleName.endsWith("r") ? roleName + "s" : roleName; + + return Text.translatableWithFallback("modmenu.credits.role." + translationKey, fallback).append(Text.literal(":")); + } + protected class DescriptionEntry extends ElementListWidget.Entry { protected OrderedText text; protected int indent; diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index 22c95d6a..2bc845c5 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -68,11 +68,17 @@ default String getTranslatedDescription() { @NotNull List getAuthors(); + /** + * @return a mapping of contributors to their roles. + */ @NotNull - List getContributors(); + Map> getContributors(); + /** + * @return a mapping of roles to each contributor with that role. + */ @NotNull - List getCredits(); + SortedMap> getCredits(); @NotNull Set getBadges(); diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java index d7d32d73..39e3ac26 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java @@ -89,13 +89,13 @@ public FabricDummyParentMod(FabricMod host, String id) { } @Override - public @NotNull List getContributors() { - return new ArrayList<>(); + public @NotNull Map> getContributors() { + return Map.of(); } @Override - public @NotNull List getCredits() { - return new ArrayList<>(); + public @NotNull SortedMap> getCredits() { + return new TreeMap<>(); } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index 57acce9f..72da944e 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -211,20 +211,35 @@ public FabricMod(ModContainer modContainer, Set modpackMods) { } @Override - public @NotNull List getContributors() { - List authors = metadata.getContributors().stream().map(Person::getName).collect(Collectors.toList()); - if ("minecraft".equals(getId()) && authors.isEmpty()) { - return Lists.newArrayList(); + public @NotNull Map> getContributors() { + Map> contributors = new HashMap<>(); + + for (var contributor : this.metadata.getContributors()) { + contributors.put(contributor.getName(), List.of("Contributor")); } - return authors; + + return contributors; } - @NotNull - public List getCredits() { - List list = new ArrayList<>(); - list.addAll(getAuthors()); - list.addAll(getContributors()); - return list; + @Override + public @NotNull SortedMap> getCredits() { + SortedMap> credits = new TreeMap<>(); + + var authors = this.getAuthors(); + var contributors = this.getContributors(); + + for (var author : authors) { + contributors.put(author, List.of("Author")); + } + + for (var contributor : contributors.entrySet()) { + for (var role : contributor.getValue()) { + credits.computeIfAbsent(role, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); + credits.get(role).add(contributor.getKey()); + } + } + + return credits; } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java index d293d9d3..ae69ba2e 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java @@ -15,9 +15,17 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; public class QuiltMod extends FabricMod { @@ -51,17 +59,30 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set getContributors() { - List authors = metadata.contributors().stream().map(modContributor -> modContributor.name() + " (" + modContributor.role() + ")").collect(Collectors.toList()); - if ("minecraft".equals(getId()) && authors.isEmpty()) { - return Lists.newArrayList(); + public @NotNull Map> getContributors() { + Map> contributors = new HashMap<>(); + + for (var contributor : this.metadata.contributors()) { + contributors.put(contributor.name(), contributor.roles()); } - return authors; + + return contributors; } @Override - public @NotNull List getCredits() { - return this.getContributors(); + public @NotNull SortedMap> getCredits() { + SortedMap> credits = new TreeMap<>(); + + var contributors = this.getContributors(); + + for (var contributor : contributors.entrySet()) { + for (var role : contributor.getValue()) { + credits.computeIfAbsent(role, key -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)); + credits.get(role).add(contributor.getKey()); + } + } + + return credits; } diff --git a/src/main/resources/assets/modmenu/lang/en_us.json b/src/main/resources/assets/modmenu/lang/en_us.json index 1c42d9fb..11f13b63 100644 --- a/src/main/resources/assets/modmenu/lang/en_us.json +++ b/src/main/resources/assets/modmenu/lang/en_us.json @@ -87,6 +87,14 @@ "modmenu.wiki": "Wiki", "modmenu.youtube": "YouTube", + "modmenu.credits.role.author": "Authors", + "modmenu.credits.role.contributor": "Contributors", + "modmenu.credits.role.translator": "Translators", + "modmenu.credits.role.maintainer": "Maintainers", + "modmenu.credits.role.playtester": "Playtesters", + "modmenu.credits.role.illustrator": "Illustrators", + "modmenu.credits.role.owner": "Owners", + "modmenu.modsFolder": "Open Mods Folder", "modmenu.configsFolder": "Open Configs Folder",