From 99ac5fc3a796e26bd66d54faa1d186938161a46a Mon Sep 17 00:00:00 2001 From: koka Date: Tue, 1 Sep 2026 18:30:53 +0200 Subject: [PATCH] Add terminal sorting modes --- CHANGELOG.md | 3 +- README.md | 2 +- build.gradle | 51 ++++++++++++++-- gradle.properties | 2 +- .../client/gui/StorageTerminalScreen.java | 33 ++++++++++- .../storage/StorageTerminalMenu.java | 7 ++- .../storage/StorageTerminalSort.java | 59 +++++++++++++++++++ .../assets/redstoner/lang/en_us.json | 4 ++ .../assets/redstoner/lang/fr_fr.json | 4 ++ .../assets/redstoner/lang/ru_ru.json | 4 ++ .../storage/StorageTerminalSmoke.java | 42 +++++++++++++ 11 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSort.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d76282..7d7c950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve ## Non publié -Module modifié : **Red-Stoner `0.0.0-alpha.15`**. +Module modifié : **Red-Stoner `0.0.0-alpha.16`**. - rend le champ de recherche du terminal inactif à l’ouverture puis focalisable au clic, libère le focus au clic extérieur et laisse toujours la touche d’inventaire fermer le terminal ; - permet à la recherche du terminal de retrouver une boîte de Shulker par le nom ou l’identifiant d’un objet qu’elle contient, tout en conservant la boîte comme résultat afin que son aperçu vanilla reste directement consultable ; +- ajoute au terminal les tris alphabétique, par quantité croissante et par quantité décroissante, appliqués explicitement sans déplacer les objets sous le curseur lors des rafraîchissements périodiques ; ## `26.2.0-alpha.227` diff --git a/README.md b/README.md index 10c6b39..d5527bf 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ L’alpha.220 transforme le terminal de stockage en véritable menu de conteneur L’alpha.219 porte l'île finie Minecraft Indev dans `sanctuary:alpha` : 128 × 128 × 64 blocs déterministes, plages, cavernes, minerais, fluides, végétation et maison historique, le tout borné sans chunks parasites. Le cadre 4 × 5 en cobblestone moussue est maintenant actif et crée une paire persistante à une arrivée sûre hors de la maison. Ambiance peut enfin composer localement Golden Days Base puis Alpha uniquement dans cette dimension, sans redistribuer leurs textures et en restaurant exactement la pile précédente. Voir le [contrat Alpha](sanctuary/ALPHA.md), le [contrat des portails](iliketomoveit/PORTALS.md) et le [guide Golden Days](ambiance/GOLDEN_DAYS.md). -L’alpha.218 ajoute le terminal de stockage Red-Stoner : cette demi-dalle verticale se fixe sur un coffre, un coffre piégé ou un baril et donne accès, par pages défilantes de 54 types, à un réseau contigu borné à 128 conteneurs chargés. Les retraits et dépôts sont validés par le serveur, les composants d’items restent strictement séparés et le terminal endgame est exclu du Shop, du Black Market et des récompenses Only Fun. La recherche retrouve aussi une boîte de Shulker d’après son contenu direct ; la boîte reste affichée dans la grille afin que son aperçu vanilla soit consultable sans modifier les règles de retrait. +L’alpha.218 ajoute le terminal de stockage Red-Stoner : cette demi-dalle verticale se fixe sur un coffre, un coffre piégé ou un baril et donne accès, par pages défilantes de 54 types, à un réseau contigu borné à 128 conteneurs chargés. Les retraits et dépôts sont validés par le serveur, les composants d’items restent strictement séparés et le terminal endgame est exclu du Shop, du Black Market et des récompenses Only Fun. La recherche retrouve aussi une boîte de Shulker d’après son contenu direct ; la boîte reste affichée dans la grille afin que son aperçu vanilla soit consultable sans modifier les règles de retrait. Un bouton permet enfin de trier explicitement les résultats par nom, quantité croissante ou quantité décroissante. L’alpha.217 tentait de compenser la hauteur trop grave du disque blanc par un facteur OpenAL de `4/3`. Cette compensation est retirée par l’alpha.221, qui corrige directement la conversion stéréo vers mono. diff --git a/build.gradle b/build.gradle index 074454c..7d90fbe 100644 --- a/build.gradle +++ b/build.gradle @@ -3692,7 +3692,7 @@ tasks.register("verifyMirrorLavenderDrawerRelease") { def drawerRecipe = file("redstoner/src/main/resources/data/redstoner/recipe/drawer.json") if (rootProject.pack_version != "26.2.0-alpha.227" || rootProject.anotherworld_version != "0.0.0-alpha.57" - || rootProject.redstoner_version != "0.0.0-alpha.15" + || rootProject.redstoner_version != "0.0.0-alpha.16" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.191" || migration.source?.modules != [anotherworld: "0.0.0-alpha.48", redstoner: "0.0.0-alpha.9"] @@ -4294,6 +4294,10 @@ project(":redstoner") { "screen.redstoner.storage_terminal.search", "screen.redstoner.storage_terminal.no_results", "screen.redstoner.storage_terminal.summary", + "screen.redstoner.storage_terminal.sort", + "screen.redstoner.storage_terminal.sort.alphabetical", + "screen.redstoner.storage_terminal.sort.count_ascending", + "screen.redstoner.storage_terminal.sort.count_descending", "screen.redstoner.storage_terminal.error.invalid_terminal", "screen.redstoner.storage_terminal.error.disconnected", "screen.redstoner.storage_terminal.error.too_large", @@ -4310,7 +4314,7 @@ project(":redstoner") { "src/main/resources/data/redstoner/recipe/controller.json")) def terminalRecipe = new JsonSlurper().parse(file( "src/main/resources/data/redstoner/recipe/storage_terminal.json")) - if (project.version.toString() != "0.0.0-alpha.15" + if (project.version.toString() != "0.0.0-alpha.16" || manifest.custom?.sanctuary26?.data_version != 3 || manifest.custom?.sanctuary26?.network_protocol != 7 || terminalMigration.source?.pack_version != "26.2.0-alpha.217" @@ -6659,7 +6663,7 @@ tasks.register("verifyAlpha218Release") { "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.227" - || rootProject.redstoner_version != "0.0.0-alpha.15" + || rootProject.redstoner_version != "0.0.0-alpha.16" || currentModules.findAll { id, version -> id != "redstoner" && id != "sanctuary" && id != "onlyfun" && id != "iliketomoveit" }.any { @@ -6848,7 +6852,7 @@ tasks.register("verifyAlpha220Release") { def networking = file( "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java").text if (rootProject.pack_version != "26.2.0-alpha.227" - || rootProject.redstoner_version != "0.0.0-alpha.15" + || rootProject.redstoner_version != "0.0.0-alpha.16" || migration.source?.pack_version != "26.2.0-alpha.219" || migration.source?.modules != [redstoner: "0.0.0-alpha.11"] || migration.target?.pack_version != "26.2.0-alpha.220" @@ -6883,7 +6887,7 @@ tasks.register("verifyAlpha220Release") { || !inventory.contains("for (Container container : containers)") || !screen.contains("extends AbstractContainerScreen") || !screen.contains("textures/gui/container/generic_54.png") - || screen.contains("Button.builder") + || screen.contains('Button.builder(Component.translatable("gui.done")') || !networking.contains("PROTOCOL_VERSION = 7") || networking.contains("storage_terminal_snapshot") || networking.contains("storage_terminal_action") @@ -7077,7 +7081,7 @@ tasks.register("verifyAlpha222Release") { it.path == "mods/jei-26.2-fabric-30.29.0.199.jar" } if (rootProject.pack_version != "26.2.0-alpha.227" - || rootProject.redstoner_version != "0.0.0-alpha.15" + || rootProject.redstoner_version != "0.0.0-alpha.16" || rootProject.sanctuary_version != "0.0.0-alpha.131" || migration.source?.pack_version != "26.2.0-alpha.221" || migration.source?.modules != [ @@ -7191,6 +7195,40 @@ tasks.register("verifyStorageTerminalSearchFocus") { } } +tasks.register("verifyStorageTerminalSorting") { + group = "verification" + description = "Checks the three server-authoritative storage-terminal sorting modes and client control." + inputs.file(file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSort.java")) + inputs.file(file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java")) + inputs.file(file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java")) + inputs.file(file( + "redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java")) + doLast { + def sort = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSort.java").text + def menu = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java").text + def screen = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java").text + def smoke = file( + "redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java").text + if (!sort.contains('ALPHABETICAL("alphabetical")') + || !sort.contains('COUNT_ASCENDING("count_ascending")') + || !sort.contains('COUNT_DESCENDING("count_descending")') + || !menu.contains("SORT_BUTTON_BASE") + || !menu.contains("sortMode.order(") + || !screen.contains("button -> requestSort()") + || !screen.contains("sortMode = sortMode.next()") + || !smoke.contains("verifySorting()") + || !smoke.contains("periodic quantity refresh")) { + throw new GradleException("The storage-terminal sorting regression contract is incomplete") + } + } +} + tasks.register("verifyAlpha223Release") { group = "verification" description = "Checks the Sanctuary 26.2.0-alpha.223 inventory-sort and quest-tracker display release." @@ -7826,6 +7864,7 @@ tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyAlpha221Release") dependsOn tasks.named("verifyAlpha222Release") dependsOn tasks.named("verifyStorageTerminalSearchFocus") + dependsOn tasks.named("verifyStorageTerminalSorting") dependsOn tasks.named("verifyAlpha223Release") dependsOn tasks.named("verifyAlpha224Release") dependsOn tasks.named("verifyAlpha225Release") diff --git a/gradle.properties b/gradle.properties index 683a1d2..b803653 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ mod_version=0.0.0-alpha.0 module_lifecycle=standby ambiance_version=0.0.0-alpha.22 ambiance_lifecycle=active -redstoner_version=0.0.0-alpha.15 +redstoner_version=0.0.0-alpha.16 redstoner_lifecycle=active anotherworld_version=0.0.0-alpha.57 anotherworld_lifecycle=active diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java index b4b5adb..fa5bc26 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java @@ -2,12 +2,15 @@ package fr.koka99cab.sanctuary26.redstoner.client.gui; import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalMenu; import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalRules; +import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalSort; import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalStatus; import java.util.ArrayList; import java.util.List; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; @@ -28,8 +31,10 @@ public final class StorageTerminalScreen extends AbstractContainerScreen requestSort()) + .bounds(leftPos + 108, topPos + 2, 34, 14) + .tooltip(Tooltip.create(sortTooltip())) + .build()); } @Override @@ -151,6 +160,28 @@ public final class StorageTerminalScreen extends AbstractContainerScreen "A→Z"; + case COUNT_ASCENDING -> "1↑"; + case COUNT_DESCENDING -> "1↓"; + }); + } + + private Component sortTooltip() { + return Component.translatable("screen.redstoner.storage_terminal.sort", + Component.translatable("screen.redstoner.storage_terminal.sort." + + sortMode.translationSuffix())); + } + private void searchChanged(String value) { String previous = sentSearchValue; searchValue = value; diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java index 839cfb9..0490663 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java @@ -27,6 +27,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { public static final int PAGE_BUTTON_BASE = 10; public static final int SEARCH_CLEAR_BUTTON = 100; public static final int SEARCH_BACKSPACE_BUTTON = 101; + public static final int SORT_BUTTON_BASE = 200; public static final int SEARCH_CODEPOINT_BUTTON_BASE = 1_000; private static final int PLAYER_INVENTORY_Y = 140; private static final int REFRESH_INTERVAL = 20; @@ -49,6 +50,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { private final StorageTerminalLayout layout = new StorageTerminalLayout(); private List entries = List.of(); private String searchQuery = ""; + private StorageTerminalSort sortMode = StorageTerminalSort.ALPHABETICAL; private int refreshCooldown = REFRESH_INTERVAL; public StorageTerminalMenu(int containerId, Inventory inventory) { @@ -131,6 +133,9 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { if (searchQuery.isEmpty()) return true; searchQuery = searchQuery.substring(0, searchQuery.offsetByCodePoints(searchQuery.length(), -1)); + } else if (buttonId >= SORT_BUTTON_BASE + && buttonId < SORT_BUTTON_BASE + StorageTerminalSort.values().length) { + sortMode = StorageTerminalSort.values()[buttonId - SORT_BUTTON_BASE]; } else { int codePoint = buttonId - SEARCH_CODEPOINT_BUTTON_BASE; if (!isSearchCodePoint(codePoint)) return false; @@ -211,7 +216,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { if (serverPlayer == null || terminalPos == null) return; discovery = StorageTerminalNetwork.discover(serverPlayer, terminalPos); List liveEntries = - StorageTerminalNetwork.filteredEntries(discovery, searchQuery); + sortMode.order(StorageTerminalNetwork.filteredEntries(discovery, searchQuery)); layout.update(liveEntries, resetOrder); int page = discovery.status() == StorageTerminalStatus.READY diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSort.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSort.java new file mode 100644 index 0000000..2c3ee02 --- /dev/null +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSort.java @@ -0,0 +1,59 @@ +package fr.koka99cab.sanctuary26.redstoner.storage; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; + +/** Explicit server-side ordering modes for terminal entries. */ +public enum StorageTerminalSort { + ALPHABETICAL("alphabetical"), + COUNT_ASCENDING("count_ascending"), + COUNT_DESCENDING("count_descending"); + + private final String translationSuffix; + + StorageTerminalSort(String translationSuffix) { + this.translationSuffix = translationSuffix; + } + + public String translationSuffix() { + return translationSuffix; + } + + public StorageTerminalSort next() { + StorageTerminalSort[] values = values(); + return values[(ordinal() + 1) % values.length]; + } + + public List order( + List entries) { + List ordered = new ArrayList<>(entries); + ordered.sort(comparator()); + return List.copyOf(ordered); + } + + private Comparator comparator() { + Comparator alphabetical = Comparator + .comparing((StorageTerminalInventory.Entry entry) -> alphabeticalName(entry.stack())) + .thenComparing(entry -> itemId(entry.stack())); + return switch (this) { + case ALPHABETICAL -> alphabetical; + case COUNT_ASCENDING -> Comparator + .comparingInt(StorageTerminalInventory.Entry::total).thenComparing(alphabetical); + case COUNT_DESCENDING -> Comparator + .comparingInt(StorageTerminalInventory.Entry::total).reversed().thenComparing(alphabetical); + }; + } + + private static String alphabeticalName(ItemStack stack) { + return StorageTerminalSearch.normalize(stack.getHoverName().getString()); + } + + private static String itemId(ItemStack stack) { + Identifier id = BuiltInRegistries.ITEM.getKey(stack.getItem()); + return id == null ? "" : id.toString(); + } +} diff --git a/redstoner/src/main/resources/assets/redstoner/lang/en_us.json b/redstoner/src/main/resources/assets/redstoner/lang/en_us.json index 7de24e6..2f7ac08 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/en_us.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/en_us.json @@ -55,6 +55,10 @@ "screen.redstoner.storage_terminal.search": "Search…", "screen.redstoner.storage_terminal.no_results": "No items found.", "screen.redstoner.storage_terminal.summary": "%s/%s · %s blocks · %s types", + "screen.redstoner.storage_terminal.sort": "Sort: %s", + "screen.redstoner.storage_terminal.sort.alphabetical": "alphabetical", + "screen.redstoner.storage_terminal.sort.count_ascending": "quantity, ascending", + "screen.redstoner.storage_terminal.sort.count_descending": "quantity, descending", "screen.redstoner.storage_terminal.error.invalid_terminal": "The terminal is no longer accessible.", "screen.redstoner.storage_terminal.error.disconnected": "No connected chest or barrel.", "screen.redstoner.storage_terminal.error.too_large": "Network too large (maximum 128 storage blocks).", diff --git a/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json b/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json index 6d4cac0..99742a3 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json @@ -55,6 +55,10 @@ "screen.redstoner.storage_terminal.search": "Rechercher…", "screen.redstoner.storage_terminal.no_results": "Aucun objet trouvé.", "screen.redstoner.storage_terminal.summary": "%s/%s · %s blocs · %s types", + "screen.redstoner.storage_terminal.sort": "Tri : %s", + "screen.redstoner.storage_terminal.sort.alphabetical": "alphabétique", + "screen.redstoner.storage_terminal.sort.count_ascending": "quantité croissante", + "screen.redstoner.storage_terminal.sort.count_descending": "quantité décroissante", "screen.redstoner.storage_terminal.error.invalid_terminal": "Le terminal n'est plus accessible.", "screen.redstoner.storage_terminal.error.disconnected": "Aucun coffre ou baril connecté.", "screen.redstoner.storage_terminal.error.too_large": "Réseau trop grand (128 stockages maximum).", diff --git a/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json b/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json index fec0373..7d77e70 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json @@ -55,6 +55,10 @@ "screen.redstoner.storage_terminal.search": "Поиск…", "screen.redstoner.storage_terminal.no_results": "Предметы не найдены.", "screen.redstoner.storage_terminal.summary": "%s/%s · блоков: %s · типов: %s", + "screen.redstoner.storage_terminal.sort": "Сортировка: %s", + "screen.redstoner.storage_terminal.sort.alphabetical": "по алфавиту", + "screen.redstoner.storage_terminal.sort.count_ascending": "по возрастанию количества", + "screen.redstoner.storage_terminal.sort.count_descending": "по убыванию количества", "screen.redstoner.storage_terminal.error.invalid_terminal": "Терминал больше недоступен.", "screen.redstoner.storage_terminal.error.disconnected": "Нет подключённых сундуков или бочек.", "screen.redstoner.storage_terminal.error.too_large": "Сеть слишком велика (не более 128 хранилищ).", diff --git a/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java b/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java index fd6c684..f48d6d5 100644 --- a/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java +++ b/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java @@ -27,6 +27,7 @@ public final class StorageTerminalSmoke { verifyPagination(); verifyAggregationAndTransfers(); verifySearch(); + verifySorting(); verifyStableLayout(); } @@ -149,6 +150,47 @@ public final class StorageTerminalSmoke { .set(DataComponents.MAX_STACK_SIZE, 64).build()); } + private static void verifySorting() { + ItemStack eclair = new ItemStack(stackable(Items.STONE)); + eclair.set(DataComponents.CUSTOM_NAME, Component.literal("Éclair")); + ItemStack banana = new ItemStack(stackable(Items.DIRT)); + banana.set(DataComponents.CUSTOM_NAME, Component.literal("Banane")); + ItemStack zebra = new ItemStack(stackable(Items.DIAMOND)); + zebra.set(DataComponents.CUSTOM_NAME, Component.literal("Zèbre")); + List entries = List.of( + new StorageTerminalInventory.Entry(eclair, 64), + new StorageTerminalInventory.Entry(banana, 8), + new StorageTerminalInventory.Entry(zebra, 2)); + + require(names(StorageTerminalSort.ALPHABETICAL.order(entries)) + .equals(List.of("Banane", "Éclair", "Zèbre")), + "Alphabetical terminal sorting ignored names or accent folding"); + require(names(StorageTerminalSort.COUNT_ASCENDING.order(entries)) + .equals(List.of("Zèbre", "Banane", "Éclair")), + "Ascending terminal quantity sorting is incorrect"); + require(names(StorageTerminalSort.COUNT_DESCENDING.order(entries)) + .equals(List.of("Éclair", "Banane", "Zèbre")), + "Descending terminal quantity sorting is incorrect"); + require(StorageTerminalSort.ALPHABETICAL.next() == StorageTerminalSort.COUNT_ASCENDING + && StorageTerminalSort.COUNT_ASCENDING.next() == StorageTerminalSort.COUNT_DESCENDING + && StorageTerminalSort.COUNT_DESCENDING.next() == StorageTerminalSort.ALPHABETICAL, + "Terminal sorting modes do not cycle deterministically"); + + StorageTerminalLayout stable = new StorageTerminalLayout(); + stable.update(StorageTerminalSort.COUNT_DESCENDING.order(entries), true); + List changedCounts = List.of( + new StorageTerminalInventory.Entry(eclair, 1), + new StorageTerminalInventory.Entry(banana, 80), + new StorageTerminalInventory.Entry(zebra, 2)); + stable.update(StorageTerminalSort.COUNT_DESCENDING.order(changedCounts), false); + require(names(stable.page(0).subList(0, 3)).equals(List.of("Éclair", "Banane", "Zèbre")), + "A periodic quantity refresh moved terminal entries under the cursor"); + } + + private static List names(List entries) { + return entries.stream().map(entry -> entry.stack().getHoverName().getString()).toList(); + } + private static void verifyStableLayout() { StorageTerminalInventory.Entry stone = new StorageTerminalInventory.Entry( new ItemStack(stackable(Items.STONE)), 64); -- 2.54.0