From 40acccc818388e38d7024d61cefd714072c3b012 Mon Sep 17 00:00:00 2001 From: koka Date: Sun, 30 Aug 2026 08:43:17 +0200 Subject: [PATCH 1/2] feat(redstoner): add storage terminal keyboard search --- CHANGELOG.md | 13 + README.md | 4 +- build.gradle | 370 ++++++++++++------ gradle.properties | 4 +- ...-storage-terminal-search-to-alpha.222.json | 52 +++ pack/packwiz/index.toml | 4 +- pack/packwiz/pack.toml | 4 +- pack/prism/modrinth.index.json | 2 +- pack/release.json | 4 +- .../client/gui/StorageTerminalScreen.java | 59 ++- .../storage/StorageTerminalInventory.java | 21 +- .../storage/StorageTerminalLayout.java | 52 +++ .../storage/StorageTerminalMenu.java | 74 +++- .../storage/StorageTerminalNetwork.java | 23 +- .../storage/StorageTerminalRules.java | 17 + .../storage/StorageTerminalSearch.java | 51 +++ .../assets/redstoner/lang/en_us.json | 2 + .../assets/redstoner/lang/fr_fr.json | 2 + .../assets/redstoner/lang/ru_ru.json | 2 + .../storage/StorageTerminalSmoke.java | 57 +++ 20 files changed, 630 insertions(+), 187 deletions(-) create mode 100644 pack/migrations/26.2.0-alpha.221-storage-terminal-search-to-alpha.222.json create mode 100644 redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java create mode 100644 redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 92de6f6..fa39236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve - Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46. - Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées. +## `26.2.0-alpha.222` + +Module modifié : **Red-Stoner `0.0.0-alpha.13`**. Les neuf autres modules conservent leur version issue de l'alpha.221. + +- ajoute dans le terminal de stockage un champ de recherche focalisé dès l’ouverture, sans bouton de fermeture ni nouvel écran propriétaire ; +- filtre les types avant la pagination à partir du nom affiché, du nom personnalisé et de l’identifiant namespacé de l’item ; +- accepte une requête Unicode bornée à 32 caractères, insensible à la casse et aux accents, avec plusieurs termes cumulables ; +- transmet les modifications par les actions natives du menu et recalcule les résultats côté serveur afin que les retraits et dépôts restent cohérents et autoritaires ; +- compacte les quantités visibles dès 1 000 (`1K`, `1.2K`, `12K`) tout en conservant le total exact dans l’infobulle ; +- fige l’identité de chaque case pendant l’ouverture du terminal : une ressource épuisée laisse une case vide et les nouvelles ressources sont ajoutées à la fin, afin que les shift-clics répétés ne basculent jamais sur l’item suivant ; +- ajoute les traductions FR/EN/RU et des tests déterministes pour les identifiants, les accents, les caractères non latins, les recherches sans résultat et la stabilité des cases ; +- conserve les données Red-Stoner v3, le protocole réseau 7, les IDs et les sauvegardes existantes ; aucun nouveau monde ni réinitialisation n’est nécessaire. + ## `26.2.0-alpha.221` Module modifié : **Ambiance `0.0.0-alpha.22`**. Les neuf autres modules conservent leur version issue de l'alpha.220. diff --git a/README.md b/README.md index 7ca97dd..ab75807 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,12 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`) Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module. -Version courante du pack distribué : **`26.2.0-alpha.221`**. Cette release distribue Ambiance **`0.0.0-alpha.22`** ; les neuf autres modules conservent leur version issue de l'alpha.220. +Version courante du pack distribué : **`26.2.0-alpha.222`**. Cette release distribue Red-Stoner **`0.0.0-alpha.13`** ; les neuf autres modules conservent leur version issue de l'alpha.221. La migration [CANA-01 à CANA-09](CANAPLIA.md) porte Canaplia vers Minecraft 26.2. It's Alive **`0.0.0-alpha.35`** enregistre les seize familles, les découvertes, hybridations, résines, effets serveur et le Livre interdit personnel, partageable temporairement sur un pupitre. Ambiance **`0.0.0-alpha.14`** compose les profils visuels bornés avec ses réglages d'accessibilité, et Only Fun **`0.0.0-alpha.21`** exclut explicitement Canaplia et les raccourcis de progression du Shop. +L’alpha.222 ajoute au terminal de stockage un champ de recherche utilisable immédiatement au clavier. Les noms personnalisés et identifiants d’items sont filtrés sans tenir compte de la casse ou des accents, avant la pagination, tout en conservant les résultats et transferts sous autorité serveur. Les quantités passent en affichage compact `K` dès 1 000, avec la valeur exacte dans l’infobulle. Les cases gardent leur ressource pendant toute l’ouverture : une ressource épuisée laisse un emplacement vide, ce qui permet de la vider au shift-clic répété sans prendre accidentellement la suivante. + L’alpha.221 corrige la cause réelle du disque blanc ralenti et trop grave : Ambiance décode désormais la source AAC en stéréo native à 48 kHz, puis moyenne explicitement chaque paire gauche/droite vers le flux mono positionnel de Minecraft. OpenAL reste à sa hauteur normale `1.0`, sans compensation artificielle ni changement de durée. L’alpha.220 transforme le terminal de stockage en véritable menu de conteneur, sans bouton de fermeture propriétaire. Les clics gauche/droit et shift-clic permettent maintenant de retirer ou déposer des piles depuis toutes les rangées d’inventaire débloquées par Sanctuary, jusqu’à six ; la grille agrégée reste non réorganisable et remplit chaque coffre ou baril dans l’ordre de découverte avant de passer au suivant. diff --git a/build.gradle b/build.gradle index daa0b40..a7db99c 100644 --- a/build.gradle +++ b/build.gradle @@ -3437,7 +3437,7 @@ tasks.register("verifyProgressivePauseMenuRelease") { [(locale): new JsonSlurper().parse(file( "sanctuary/src/main/resources/assets/sanctuary/lang/${locale}.json"))] } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.sanctuary_version != "0.0.0-alpha.122" || rootProject.onlyfun_version != "0.0.0-alpha.25" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version @@ -3487,9 +3487,9 @@ tasks.register("verifyMirrorLavenderDrawerRelease") { def redStonerRegistry = file( "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/registry/RedStonerRegistries.java").text def drawerRecipe = file("redstoner/src/main/resources/data/redstoner/recipe/drawer.json") - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" - || rootProject.redstoner_version != "0.0.0-alpha.12" + || rootProject.redstoner_version != "0.0.0-alpha.13" || 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"] @@ -4086,6 +4086,8 @@ project(":redstoner") { "screen.redstoner.personal_computer.shutdown", "message.redstoner.personal_computer.atlas_marker_exists", "screen.redstoner.storage_terminal.title", + "screen.redstoner.storage_terminal.search", + "screen.redstoner.storage_terminal.no_results", "screen.redstoner.storage_terminal.summary", "screen.redstoner.storage_terminal.error.invalid_terminal", "screen.redstoner.storage_terminal.error.disconnected", @@ -4103,7 +4105,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.12" + if (project.version.toString() != "0.0.0-alpha.13" || manifest.custom?.sanctuary26?.data_version != 3 || manifest.custom?.sanctuary26?.network_protocol != 7 || terminalMigration.source?.pack_version != "26.2.0-alpha.217" @@ -4144,7 +4146,7 @@ project(":redstoner") { P: "anotherworld:silver_plate", G: "minecraft:tinted_glass", C: "redstoner:controller", T: "anotherworld:titanium_ingot"] || terminalRecipe.result != [id: "redstoner:storage_terminal", count: 1]) { - throw new GradleException("Red-Stoner alpha.12 Storage Terminal menu contract is incomplete") + throw new GradleException("Red-Stoner alpha.13 Storage Terminal contract is incomplete") } if (file("src/main/resources/data/redstoner/recipe/personal_computer.json").exists() || file("src/main/resources/data/redstoner/recipe/drawer.json").exists() @@ -4275,7 +4277,7 @@ tasks.register("verifyShopPricingLayoutRelease") { "pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json")) def release = new JsonSlurper().parse(file("pack/release.json")) def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.onlyfun_version != "0.0.0-alpha.25" || rootProject.sanctuary_version != "0.0.0-alpha.122" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version @@ -4320,7 +4322,7 @@ tasks.register("verifyCuisineCropsRelease") { def climate = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ClimateCropForaging.java").text def rice = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/RiceCropBlock.java").text def bean = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/BeanCropBlock.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.itsalive_version != "0.0.0-alpha.36" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.193" @@ -4369,7 +4371,7 @@ tasks.register("verifyShopCsvBossGatesRelease") { def bossGate = file("onlyfun/src/main/java/fr/koka99cab/sanctuary26/onlyfun/shop/OnlyFunBossShopGate.java").text def catalog = file("onlyfun/src/main/java/fr/koka99cab/sanctuary26/onlyfun/shop/OnlyFunShopCatalog.java").text def offerCatalog = file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.onlyfun_version != "0.0.0-alpha.25" || rootProject.sanctuary_version != "0.0.0-alpha.122" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version @@ -4415,7 +4417,7 @@ tasks.register("verifyCulinaryMailboxGrappleFixesRelease") { def release = new JsonSlurper().parse(file("pack/release.json")) def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) def packwiz = file("pack/packwiz/pack.toml").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || migration.source?.pack_version != "26.2.0-alpha.195" || migration.target?.pack_version != "26.2.0-alpha.196" || migration.target?.modules != [ @@ -4433,10 +4435,10 @@ tasks.register("verifyCulinaryMailboxGrappleFixesRelease") { || migration.distribution?.jei?.target != "30.26.0.182" || migration.compatibility?.save_migration_required != false || migration.compatibility?.registry_ids_changed != false - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"')) { + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"')) { throw new GradleException("The alpha.196 culinary, mailbox and grapple release contract is incomplete") } } @@ -4461,7 +4463,7 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") { def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) def packwiz = file("pack/packwiz/pack.toml").text def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.sanctuary_version != "0.0.0-alpha.122" || migration.source?.pack_version != "26.2.0-alpha.196" @@ -4481,10 +4483,10 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") { || migration.distribution?.jei?.target != "30.26.0.186" || migration.compatibility?.sanctuary_target_network_protocol != 19 || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"')) { + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"')) { throw new GradleException("The alpha.197 quest-request and cauldron-batch release contract is incomplete") } } @@ -4511,7 +4513,7 @@ tasks.register("verifyFermentationBarrelAutomationRelease") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def itsAliveManifest = new JsonSlurper().parse(file("itsalive/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.itsalive_version != "0.0.0-alpha.36" || migration.source?.pack_version != "26.2.0-alpha.197" || migration.target?.pack_version != "26.2.0-alpha.198" @@ -4530,10 +4532,10 @@ tasks.register("verifyFermentationBarrelAutomationRelease") { || migration.distribution?.packwiz_index_refreshed != true || itsAliveManifest.custom?.sanctuary26?.data_version != 3 || itsAliveManifest.custom?.sanctuary26?.network_protocol != 4 - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"') + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.36.jar"')) { throw new GradleException("The alpha.198 fermentation barrel release contract is incomplete") } @@ -4561,7 +4563,7 @@ tasks.register("verifyQuestRequestContractsRelease") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.sanctuary_version != "0.0.0-alpha.122" || migration.source?.pack_version != "26.2.0-alpha.198" @@ -4575,10 +4577,10 @@ tasks.register("verifyQuestRequestContractsRelease") { || migration.quest_request_contracts?.chat_accept_revalidates_distance_owner_sign_and_claim != true || migration.compatibility?.sanctuary_target_network_protocol != 20 || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"') + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.36.jar"') || !packwizIndex.contains('file = "mods/sanctuary-0.0.0-alpha.122.jar"')) { throw new GradleException("The alpha.199 quest-request contract release is incomplete") @@ -4616,7 +4618,7 @@ tasks.register("verifyWeatherTntAmbianceRelease") { def ambianceManifest = new JsonSlurper().parse(file("ambiance/src/main/resources/fabric.mod.json")) def ouchManifest = new JsonSlurper().parse(file("ouch/src/main/resources/fabric.mod.json")) def rootBuild = file("build.gradle").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || rootProject.ouch_version != "0.0.0-alpha.10" || migration.source?.pack_version != "26.2.0-alpha.199" @@ -4650,10 +4652,10 @@ tasks.register("verifyWeatherTntAmbianceRelease") { || !weatherTnt.contains("AmbianceDailyWeather.CLEAR") || !weatherTnt.contains("CLEAR_WEATHER_TICKS = 864_000") || weatherTnt.contains("setWeatherParameters") - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"') + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || !packwizIndex.contains('file = "mods/ouch-0.0.0-alpha.10.jar"')) { throw new GradleException("The alpha.200 Weather TNT and Ambiance release is incomplete") @@ -4682,7 +4684,7 @@ tasks.register("verifyQuestRequestEscrowRelease") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.sanctuary_version != "0.0.0-alpha.122" || migration.source?.pack_version != "26.2.0-alpha.200" || migration.source?.modules?.sanctuary != "0.0.0-alpha.115" @@ -4697,10 +4699,10 @@ tasks.register("verifyQuestRequestEscrowRelease") { || migration.compatibility?.sanctuary_target_network_protocol != 21 || sanctuaryManifest.custom?.sanctuary26?.data_version != 11 || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"') + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/sanctuary-0.0.0-alpha.122.jar"')) { throw new GradleException("The alpha.201 quest-request escrow release is incomplete") } @@ -4736,7 +4738,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { "iliketomoveit/src/main/resources/fabric.mod.json")) def sanctuaryManifest = new JsonSlurper().parse(file( "sanctuary/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.iliketomoveit_version != "0.0.0-alpha.16" @@ -4776,10 +4778,10 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { || moveItManifest.custom?.sanctuary26?.network_protocol != 5 || sanctuaryManifest.custom?.sanctuary26?.data_version != 11 || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"') + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.55.jar"') || !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.36.jar"') || !packwizIndex.contains('file = "mods/iliketomoveit-0.0.0-alpha.16.jar"') @@ -4896,7 +4898,7 @@ tasks.register("verifySafeVaultFeature") { def image = javax.imageio.ImageIO.read(file("anotherworld/src/main/resources/assets/anotherworld/textures/${path}")) image == null || image.width != size[0] || image.height != size[1] }.keySet() - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || rootProject.sanctuary_version != "0.0.0-alpha.122" || migration.schema_version != 1 @@ -5088,7 +5090,7 @@ tasks.register("verifySafeVaultInterfaceAlpha203Release") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.202" || migration.source?.modules != [anotherworld: "0.0.0-alpha.50"] @@ -5106,9 +5108,9 @@ tasks.register("verifySafeVaultInterfaceAlpha203Release") { || migration.distribution?.jei?.modrinth_version_id != "XXTFqqwG" || migration.distribution?.jei?.prism_manifest_updated != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.55.jar"') || packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.50.jar"') || jeiFiles.size() != 1 @@ -5134,7 +5136,7 @@ tasks.register("verifySafeVaultFixAlpha204Release") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.203" || migration.source?.modules != [anotherworld: "0.0.0-alpha.51"] @@ -5160,9 +5162,9 @@ tasks.register("verifySafeVaultFixAlpha204Release") { || migration.distribution?.jei?.modrinth_version_id != "OcMayhUH" || migration.distribution?.jei?.prism_manifest_updated != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.55.jar"') || packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.51.jar"') || jeiFiles.size() != 1 @@ -5188,7 +5190,7 @@ tasks.register("verifySafeVaultCubeAlpha205Release") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.204" || migration.source?.modules != [anotherworld: "0.0.0-alpha.52"] @@ -5217,9 +5219,9 @@ tasks.register("verifySafeVaultCubeAlpha205Release") { || migration.distribution?.jei?.modrinth_version_id != "NtfJDMhk" || migration.distribution?.jei?.prism_manifest_updated != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.55.jar"') || packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.52.jar"') || jeiFiles.size() != 1 @@ -5246,7 +5248,7 @@ tasks.register("verifySafeVaultCtmAlpha206Release") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.205" || migration.source?.modules != [anotherworld: "0.0.0-alpha.53"] @@ -5269,9 +5271,9 @@ tasks.register("verifySafeVaultCtmAlpha206Release") { || migration.compatibility?.pack_release_updated != true || migration.distribution?.packwiz_index_refreshed != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.55.jar"') || packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.53.jar"') || jeiFiles.size() != 1 @@ -5298,7 +5300,7 @@ tasks.register("verifySafeVaultUnlimitedAlpha207Release") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.anotherworld_version != "0.0.0-alpha.55" || migration.source?.pack_version != "26.2.0-alpha.206" || migration.source?.modules != [anotherworld: "0.0.0-alpha.54"] @@ -5328,9 +5330,9 @@ tasks.register("verifySafeVaultUnlimitedAlpha207Release") { || migration.compatibility?.pack_release_updated != true || migration.distribution?.packwiz_index_refreshed != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.55.jar"') || packwizIndex.contains('file = "mods/anotherworld-0.0.0-alpha.54.jar"') || jeiFiles.size() != 1 @@ -5386,7 +5388,7 @@ tasks.register("verifyCanapliaFoundation") { "itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/ItsAliveMod.java").text def visualApiSource = file( "ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.issue != 35 @@ -5638,7 +5640,7 @@ tasks.register("verifyCanapliaAlpha209Release") { ambiance: "0.0.0-alpha.14", itsalive: "0.0.0-alpha.33", onlyfun: "0.0.0-alpha.20"] - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.onlyfun_version != "0.0.0-alpha.25" @@ -5686,9 +5688,9 @@ tasks.register("verifyCanapliaAlpha209Release") { || fixMigration.compatibility?.network_protocol_changed != false || fixMigration.distribution?.pack_release_updated != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.36.jar"') || !packwizIndex.contains('file = "mods/onlyfun-0.0.0-alpha.25.jar"') @@ -5756,7 +5758,7 @@ tasks.register("verifyAlpha210Release") { def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.sanctuary_version != "0.0.0-alpha.122" || rootProject.onlyfun_version != "0.0.0-alpha.25" || rootProject.iliketomoveit_version != currentModules.iliketomoveit @@ -5802,10 +5804,10 @@ tasks.register("verifyAlpha210Release") { || manifests.iliketomoveit.custom?.sanctuary26?.network_protocol != 5 || manifests.itsalive.custom?.sanctuary26?.data_version != 3 || manifests.itsalive.custom?.sanctuary26?.network_protocol != 4 - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" - || !packwiz.contains('version = "26.2.0-alpha.221"') + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" + || !packwiz.contains('version = "26.2.0-alpha.222"') || requiredJars.any { !packwizIndex.contains(it) } || obsoleteJars.any { packwizIndex.contains(it) } || jeiFiles.size() != 1 @@ -5881,7 +5883,7 @@ tasks.register("verifyAlpha211Release") { def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || workspaceModules.any { id, version -> rootProject.property("${id}_version") != version } || migration.source?.pack_version != "26.2.0-alpha.210" || migration.source?.modules != expectedSource @@ -5909,9 +5911,9 @@ tasks.register("verifyAlpha211Release") { || migration.distribution?.prism_modrinth_index_updated != true || migration.distribution?.shared_client_server_mrpack != true || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || requiredJars.any { !packwizIndex.contains(it) } || obsoleteJars.any { packwizIndex.contains(it) } || prism.files?.size() != 5 @@ -5919,7 +5921,7 @@ tasks.register("verifyAlpha211Release") { || jeiFiles[0].hashes?.sha1 != "494f9dc787d6c9cd9d32793da2784ab16834faf3" || jeiFiles[0].hashes?.sha512 != "0869f4d5840a732a04bedc7684b40140f087f3d759066c206d9d1ec710007f7c9585e4f5ebb04f10e56bef1cf2eddc6978bb2b2c93541cff5fcf7e1609859468" || !changelog.contains("## `26.2.0-alpha.211`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.211 Abjection and gameplay-polish release is incomplete") } } @@ -5953,7 +5955,7 @@ tasks.register("verifyAlpha212Release") { def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.sanctuary_version != "0.0.0-alpha.122" || migration.source?.pack_version != "26.2.0-alpha.211" || migration.source?.modules != [sanctuary: "0.0.0-alpha.119"] @@ -5975,16 +5977,16 @@ tasks.register("verifyAlpha212Release") { || manifest.custom?.sanctuary26?.data_version != 11 || manifest.custom?.sanctuary26?.network_protocol != 22 || !minecraftMixin.contains('method = "pickBlockOrEntity"') - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" || prism.files?.size() != 5 || jeiFiles.size() != 1 - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/sanctuary-0.0.0-alpha.122.jar"') || packwizIndex.contains('file = "mods/sanctuary-0.0.0-alpha.119.jar"') || !changelog.contains("## `26.2.0-alpha.212`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.212 Black Market startup-fix release is incomplete") } } @@ -6018,7 +6020,7 @@ tasks.register("verifyAlpha213Release") { def jeiFiles = prism.files.findAll { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.source?.pack_version != "26.2.0-alpha.212" || migration.source?.modules != [ambiance: "0.0.0-alpha.15"] @@ -6042,16 +6044,16 @@ tasks.register("verifyAlpha213Release") { || manifest.custom?.sanctuary26?.network_protocol != 6 || !levelEventMixin.contains('@Mixin(LevelEventHandler.class)') || !levelEventMixin.contains('@Inject(method = "levelEvent"') - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" || prism.files?.size() != 5 || jeiFiles.size() != 1 - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.15.jar"') || !changelog.contains("## `26.2.0-alpha.213`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.213 Ambiance startup-fix release is incomplete") } } @@ -6086,7 +6088,7 @@ tasks.register("verifyAlpha214Release") { "ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscAudioPlayer.java").text def urlSmoke = file( "ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/white_disc/WhiteDiscUrlSmoke.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.source?.pack_version != "26.2.0-alpha.213" || migration.source?.modules != [ambiance: "0.0.0-alpha.16"] @@ -6115,16 +6117,16 @@ tasks.register("verifyAlpha214Release") { || !audioPlayer.contains('message.ambiance.white_disc.loading') || !audioPlayer.contains('message.ambiance.white_disc.failed') || !urlSmoke.contains("https://youtu.be/Ue5ZBe-GzSM?si=78LSbtBQzUYzhA2Y") - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" || prism.files?.size() != 5 - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.16.jar"') || !changelog.contains("## `26.2.0-alpha.214`") || changelog.contains("## Modifications non distribuées") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.214 white-disc solo synchronization release is incomplete") } } @@ -6165,7 +6167,7 @@ tasks.register("verifyAlpha215Release") { "ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/LavaplayerAudioStream.java").text def playbackSmoke = file( "ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscPlaybackSmoke.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.source?.pack_version != "26.2.0-alpha.214" || migration.source?.modules != [ambiance: "0.0.0-alpha.17"] @@ -6205,16 +6207,16 @@ tasks.register("verifyAlpha215Release") { || !playbackSmoke.contains("https://www.youtube.com/watch?v=Ue5ZBe-GzSM") || !playbackSmoke.contains("stream.paddedSilenceBytes() != 0L") || !playbackSmoke.contains("stream.decodedTimelineMillis() != 4_000L") - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" || prism.files?.size() != 5 - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.17.jar"') || !changelog.contains("## `26.2.0-alpha.215`") || changelog.contains("## Modifications non distribuées") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.215 resilient white-disc playback release is incomplete") } } @@ -6259,7 +6261,7 @@ tasks.register("verifyAlpha216Release") { "ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/client/audio/LavaplayerAudioStreamSmoke.java").text def playbackSmoke = file( "ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscPlaybackSmoke.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.source?.pack_version != "26.2.0-alpha.215" || migration.source?.modules != [ambiance: "0.0.0-alpha.18"] @@ -6303,16 +6305,16 @@ tasks.register("verifyAlpha216Release") { || !playbackSmoke.contains("stream.paddedSilenceBytes() != 0L") || !playbackSmoke.contains("stream.decodedTimelineMillis() != 4_000L") || !playbackSmoke.contains("LavaplayerAudioStream.SAMPLE_RATE * 2 * 4") - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" || prism.files?.size() != 5 - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.18.jar"') || !changelog.contains("## `26.2.0-alpha.216`") || changelog.contains("## Modifications non distribuées") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.216 white-disc audio-quality release is incomplete") } } @@ -6347,7 +6349,7 @@ tasks.register("verifyAlpha217Release") { "ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscAudioPlayer.java").text def streamSmoke = file( "ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/client/audio/LavaplayerAudioStreamSmoke.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.source?.pack_version != "26.2.0-alpha.216" || migration.source?.modules != [ambiance: "0.0.0-alpha.19"] @@ -6378,16 +6380,16 @@ tasks.register("verifyAlpha217Release") { || !audioPlayer.contains("PLAYBACK_PITCH = 1.0F") || audioPlayer.count("channel.setPitch(PLAYBACK_PITCH)") != 2 || !streamSmoke.contains("WhiteDiscAudioPlayer.PLAYBACK_PITCH - 1.0F") - || release.pack_version != "26.2.0-alpha.221" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" - || prism.versionId != "26.2.0-alpha.221" + || release.pack_version != "26.2.0-alpha.222" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != "26.2.0-alpha.222" || prism.files?.size() != 5 - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.19.jar"') || !changelog.contains("## `26.2.0-alpha.217`") || changelog.contains("## Modifications non distribuées") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.217 white-disc pitch release is incomplete") } } @@ -6434,7 +6436,7 @@ tasks.register("verifyAlpha218Release") { redstoner: "0.0.0-alpha.10", onlyfun: "0.0.0-alpha.24", sanctuary: "0.0.0-alpha.120"] def targetModules = [ redstoner: "0.0.0-alpha.11", onlyfun: "0.0.0-alpha.25", sanctuary: "0.0.0-alpha.121"] - def currentModules = targetModules + [redstoner: "0.0.0-alpha.12", + def currentModules = targetModules + [redstoner: "0.0.0-alpha.13", sanctuary: "0.0.0-alpha.122", iliketomoveit: "0.0.0-alpha.16", ambiance: "0.0.0-alpha.22"] def requiredJars = currentModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" @@ -6442,7 +6444,7 @@ tasks.register("verifyAlpha218Release") { def obsoleteJars = sourceModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || currentModules.any { id, version -> rootProject.property("${id}_version") != version } || migration.source?.pack_version != "26.2.0-alpha.217" || migration.source?.modules != sourceModules @@ -6475,13 +6477,13 @@ tasks.register("verifyAlpha218Release") { || !onlyFunPolicy.contains('namespace.equals("redstoner") && path.equals("storage_terminal")') || !blackMarketPolicy.contains('namespace.equals("redstoner") && path.equals("storage_terminal")') || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || requiredJars.any { !packwizIndex.contains(it) } || obsoleteJars.any { packwizIndex.contains(it) } || !changelog.contains("## `26.2.0-alpha.218`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.218 Storage Terminal release is incomplete") } } @@ -6540,7 +6542,7 @@ tasks.register("verifyAlpha219Release") { include "**/*golden*days*.zip", "**/*golden*days*.jar" exclude "**/build/**", "**/.gradle/**", "**/.git/**" }.files - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || currentModules.any { id, version -> rootProject.property("${id}_version") != version } || migration.source?.pack_version != "26.2.0-alpha.218" || migration.source?.modules != sourceModules @@ -6572,15 +6574,15 @@ tasks.register("verifyAlpha219Release") { || manifests.ambiance.custom?.sanctuary26?.data_version != 2 || manifests.ambiance.custom?.sanctuary26?.network_protocol != 6 || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version || prism.files.any { it.path.toLowerCase().contains("golden-days") } - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || requiredJars.any { !packwizIndex.contains(it) } || obsoleteJars.any { packwizIndex.contains(it) } || !forbiddenGoldenArchives.isEmpty() || !changelog.contains("## `26.2.0-alpha.219`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.219 finite Alpha/Indev release is incomplete") } } @@ -6619,8 +6621,8 @@ tasks.register("verifyAlpha220Release") { "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java").text def networking = file( "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java").text - if (rootProject.pack_version != "26.2.0-alpha.221" - || rootProject.redstoner_version != "0.0.0-alpha.12" + if (rootProject.pack_version != "26.2.0-alpha.222" + || rootProject.redstoner_version != "0.0.0-alpha.13" || 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" @@ -6660,13 +6662,13 @@ tasks.register("verifyAlpha220Release") { || networking.contains("storage_terminal_snapshot") || networking.contains("storage_terminal_action") || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') - || !packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.12.jar"') + || !packwiz.contains('version = "26.2.0-alpha.222"') + || !packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.13.jar"') || packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.11.jar"') || !changelog.contains("## `26.2.0-alpha.220`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.220 Storage Terminal menu release is incomplete") } } @@ -6714,7 +6716,7 @@ tasks.register("verifyAlpha221Release") { def playbackSmoke = file( "ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscPlaybackSmoke.java").text def contract = migration.white_disc_native_rate - if (rootProject.pack_version != "26.2.0-alpha.221" + if (rootProject.pack_version != "26.2.0-alpha.222" || rootProject.ambiance_version != "0.0.0-alpha.22" || migration.source?.pack_version != "26.2.0-alpha.220" || migration.source?.modules != [ambiance: "0.0.0-alpha.21"] @@ -6763,18 +6765,131 @@ tasks.register("verifyAlpha221Release") { || !playbackSmoke.contains("PCM cadence: 100%% speed") || !whiteDiscDocumentation.contains("48 000 Hz") || release.pack_version != rootProject.pack_version - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" || prism.versionId != rootProject.pack_version - || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwiz.contains('version = "26.2.0-alpha.222"') || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.22.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.21.jar"') || !changelog.contains("## `26.2.0-alpha.221`") - || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.221`**")) { + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { throw new GradleException("The alpha.221 native-rate white-disc release is incomplete") } } } +tasks.register("verifyAlpha222Release") { + group = "verification" + description = "Checks the Sanctuary 26.2.0-alpha.222 Storage Terminal keyboard-search release." + inputs.file(file("pack/migrations/26.2.0-alpha.221-storage-terminal-search-to-alpha.222.json")) + inputs.file(file("pack/release.json")) + inputs.file(file("pack/prism/modrinth.index.json")) + inputs.file(file("pack/packwiz/pack.toml")) + inputs.file(file("pack/packwiz/index.toml")) + inputs.file(file("CHANGELOG.md")) + inputs.file(file("README.md")) + inputs.file(file("redstoner/src/main/resources/fabric.mod.json")) + 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/storage/StorageTerminalNetwork.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java")) + inputs.file(file("redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java")) + doLast { + def migration = new JsonSlurper().parse(file( + "pack/migrations/26.2.0-alpha.221-storage-terminal-search-to-alpha.222.json")) + def release = new JsonSlurper().parse(file("pack/release.json")) + def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) + def packwiz = file("pack/packwiz/pack.toml").text + def packwizIndex = file("pack/packwiz/index.toml").text + def changelog = file("CHANGELOG.md").text + def readme = file("README.md").text + def manifest = new JsonSlurper().parse(file("redstoner/src/main/resources/fabric.mod.json")) + def menu = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java").text + def network = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java").text + def rules = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java").text + def layout = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java").text + def search = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java").text + def screen = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java").text + def networking = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java").text + def smoke = file( + "redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java").text + def contract = migration.storage_terminal_search + if (rootProject.pack_version != "26.2.0-alpha.222" + || rootProject.redstoner_version != "0.0.0-alpha.13" + || migration.source?.pack_version != "26.2.0-alpha.221" + || migration.source?.modules != [redstoner: "0.0.0-alpha.12"] + || migration.target?.pack_version != "26.2.0-alpha.222" + || migration.target?.modules != [redstoner: "0.0.0-alpha.13"] + || contract?.owner != "redstoner" + || contract?.id != "redstoner:storage_terminal" + || contract?.keyboard_focused_on_open != true + || contract?.maximum_query_length != 32 + || contract?.unicode_input != true + || contract?.case_insensitive != true + || contract?.accent_insensitive != true + || contract?.multiple_terms_required != true + || contract?.matches_custom_names != true + || contract?.matches_item_identifiers != true + || contract?.filter_before_pagination != true + || contract?.server_authoritative_results != true + || contract?.server_authoritative_transfers != true + || contract?.visible_quantity_format != "exact_below_1000_then_decimal_k" + || contract?.exact_quantity_in_tooltip != true + || contract?.stable_slot_identity_during_session != true + || contract?.depleted_slot != "empty_until_layout_reset" + || contract?.new_resource_position != "append" + || contract?.layout_reset_on != ["search_change", "menu_reopen"] + || contract?.dedicated_search_payload != false + || contract?.network_protocol_changed != false + || migration.compatibility?.new_world_required != false + || migration.compatibility?.save_reset_required != false + || migration.compatibility?.existing_saves_supported != true + || migration.compatibility?.registry_ids_changed != false + || migration.compatibility?.redstoner_data_version != 3 + || migration.compatibility?.redstoner_network_protocol != 7 + || migration.distribution?.pack_release_updated != true + || migration.distribution?.packwiz_index_refreshed != true + || migration.distribution?.prism_modrinth_index_updated != true + || migration.distribution?.shared_client_server_mrpack != true + || manifest.custom?.sanctuary26?.data_version != 3 + || manifest.custom?.sanctuary26?.network_protocol != 7 + || !rules.contains("MAX_SEARCH_LENGTH = 32") + || !rules.contains("compactCount(int count)") + || !layout.contains("Keeps terminal slot identities stable") + || !layout.contains("new StorageTerminalInventory.Entry(identity.representative(), 0)") + || !menu.contains("SEARCH_CODEPOINT_BUTTON_BASE") + || !menu.contains("isSearchCodePoint") + || !network.contains("StorageTerminalSearch.filter") + || !search.contains("Normalizer.Form.NFD") + || !search.contains("BuiltInRegistries.ITEM.getKey") + || !screen.contains("new EditBox") + || !screen.contains("setInitialFocus(searchBox)") + || !screen.contains("searchBox.setResponder(this::searchChanged)") + || !smoke.contains("verifySearch()") + || !networking.contains("PROTOCOL_VERSION = 7") + || networking.contains("storage_terminal_search") + || release.pack_version != rootProject.pack_version + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.222.mrpack" + || prism.versionId != rootProject.pack_version + || !packwiz.contains('version = "26.2.0-alpha.222"') + || !packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.13.jar"') + || packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.12.jar"') + || !changelog.contains("## `26.2.0-alpha.222`") + || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.222`**")) { + throw new GradleException("The alpha.222 Storage Terminal keyboard-search release is incomplete") + } + } +} + tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyAlpha213Release") dependsOn tasks.named("verifyAlpha214Release") @@ -6785,6 +6900,7 @@ tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyAlpha219Release") dependsOn tasks.named("verifyAlpha220Release") dependsOn tasks.named("verifyAlpha221Release") + dependsOn tasks.named("verifyAlpha222Release") } tasks.named("check") { diff --git a/gradle.properties b/gradle.properties index 4347b3c..1e63c83 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.12 +redstoner_version=0.0.0-alpha.13 redstoner_lifecycle=active anotherworld_version=0.0.0-alpha.55 anotherworld_lifecycle=active @@ -30,5 +30,5 @@ onlyfun_version=0.0.0-alpha.25 onlyfun_lifecycle=active sanctuary_version=0.0.0-alpha.122 sanctuary_lifecycle=active -pack_version=26.2.0-alpha.221 +pack_version=26.2.0-alpha.222 maven_group=fr.koka99cab.sanctuary26 diff --git a/pack/migrations/26.2.0-alpha.221-storage-terminal-search-to-alpha.222.json b/pack/migrations/26.2.0-alpha.221-storage-terminal-search-to-alpha.222.json new file mode 100644 index 0000000..ff9b8e1 --- /dev/null +++ b/pack/migrations/26.2.0-alpha.221-storage-terminal-search-to-alpha.222.json @@ -0,0 +1,52 @@ +{ + "schema_version": 1, + "source": { + "pack_version": "26.2.0-alpha.221", + "modules": { + "redstoner": "0.0.0-alpha.12" + } + }, + "target": { + "pack_version": "26.2.0-alpha.222", + "modules": { + "redstoner": "0.0.0-alpha.13" + } + }, + "storage_terminal_search": { + "owner": "redstoner", + "id": "redstoner:storage_terminal", + "keyboard_focused_on_open": true, + "maximum_query_length": 32, + "unicode_input": true, + "case_insensitive": true, + "accent_insensitive": true, + "multiple_terms_required": true, + "matches_custom_names": true, + "matches_item_identifiers": true, + "filter_before_pagination": true, + "server_authoritative_results": true, + "server_authoritative_transfers": true, + "visible_quantity_format": "exact_below_1000_then_decimal_k", + "exact_quantity_in_tooltip": true, + "stable_slot_identity_during_session": true, + "depleted_slot": "empty_until_layout_reset", + "new_resource_position": "append", + "layout_reset_on": ["search_change", "menu_reopen"], + "dedicated_search_payload": false, + "network_protocol_changed": false + }, + "compatibility": { + "new_world_required": false, + "save_reset_required": false, + "existing_saves_supported": true, + "registry_ids_changed": false, + "redstoner_data_version": 3, + "redstoner_network_protocol": 7 + }, + "distribution": { + "pack_release_updated": true, + "packwiz_index_refreshed": true, + "prism_modrinth_index_updated": true, + "shared_client_server_mrpack": true + } +} diff --git a/pack/packwiz/index.toml b/pack/packwiz/index.toml index 4ffb219..2deefd3 100644 --- a/pack/packwiz/index.toml +++ b/pack/packwiz/index.toml @@ -60,8 +60,8 @@ file = "mods/ouch-0.0.0-alpha.10.jar" hash = "0c2e0e2ef7f6c944d0b6d5609c42431a0a31f1d92087c3b5c28b67f76795d8cf" [[files]] -file = "mods/redstoner-0.0.0-alpha.12.jar" -hash = "fee65e8d865f918ec8174b7a808762658ed91b566af590d99a3448f9d237175a" +file = "mods/redstoner-0.0.0-alpha.13.jar" +hash = "54049bca05729eff90c1e9b58687683a25b1919a158606018e2e2fceb81f203e" [[files]] file = "mods/sanctuary-0.0.0-alpha.122.jar" diff --git a/pack/packwiz/pack.toml b/pack/packwiz/pack.toml index f03f11d..14dabbf 100644 --- a/pack/packwiz/pack.toml +++ b/pack/packwiz/pack.toml @@ -1,12 +1,12 @@ name = "Sanctuary 26.2" author = "KOKA99CAB" -version = "26.2.0-alpha.221" +version = "26.2.0-alpha.222" pack-format = "packwiz:1.1.0" [index] file = "index.toml" hash-format = "sha256" -hash = "60e8342eef216dd225488fe4225a28c9b9940858642cbfc8025541affd150ab0" +hash = "98b08a8e4263a57740629696fc51d0ff43f87d5219918cfde187e7bb1f521d92" [versions] fabric = "0.19.3" diff --git a/pack/prism/modrinth.index.json b/pack/prism/modrinth.index.json index 59f830d..e8ec762 100644 --- a/pack/prism/modrinth.index.json +++ b/pack/prism/modrinth.index.json @@ -3,7 +3,7 @@ "game": "minecraft", "name": "Sanctuary 26.2", "summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.", - "versionId": "26.2.0-alpha.221", + "versionId": "26.2.0-alpha.222", "files": [ { "path": "mods/fabric-api-0.158.0+26.2.jar", diff --git a/pack/release.json b/pack/release.json index c848346..df90369 100644 --- a/pack/release.json +++ b/pack/release.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "pack_version": "26.2.0-alpha.221", + "pack_version": "26.2.0-alpha.222", "release_channel": "alpha", "minecraft_version": "26.2", "fabric_loader_version": "0.19.3", @@ -9,7 +9,7 @@ "project_id": "FgOqyAXd", "project_slug": "sanctuary-koka99", "project_url": "https://modrinth.com/modpack/sanctuary-koka99", - "primary_file": "sanctuary-26.2.0-alpha.221.mrpack", + "primary_file": "sanctuary-26.2.0-alpha.222.mrpack", "shared_client_server_pack": true, "update_owner": "packwiz-installer", "check_on_launch": true, 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 f9a94a9..ccf9f8c 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 @@ -1,12 +1,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.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.EditBox; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; @@ -24,6 +27,9 @@ public final class StorageTerminalScreen extends AbstractContainerScreen 1) { - String label = compactCount(total); + String label = StorageTerminalRules.compactCount(total); graphics.text(font, label, slot.x + 17 - font.width(label), slot.y + 9, 0xFFFFFFFF, true); } @@ -111,6 +125,15 @@ public final class StorageTerminalScreen extends AbstractContainerScreen sendMenuButton( + StorageTerminalMenu.SEARCH_CODEPOINT_BUTTON_BASE + codePoint)); + } + sentSearchValue = value; + } + + private void sendMenuButton(int buttonId) { + if (minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, buttonId); + } + } + private void extractScrollBar(GuiGraphicsExtractor graphics) { int x = leftPos + SCROLL_X; int y = topPos + TERMINAL_TOP; @@ -137,9 +185,4 @@ public final class StorageTerminalScreen extends AbstractContainerScreen= 1_000_000) return (count / 1_000_000) + "m"; - if (count >= 10_000) return (count / 1_000) + "k"; - return Integer.toString(count); - } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java index 7f3f112..06a1550 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java @@ -12,12 +12,12 @@ public final class StorageTerminalInventory { } public static List aggregate(List containers) { - Map grouped = new LinkedHashMap<>(); + Map grouped = new LinkedHashMap<>(); for (Container container : containers) { for (int slot = 0; slot < container.getContainerSize(); slot++) { ItemStack stack = container.getItem(slot); if (stack.isEmpty()) continue; - StackKey key = new StackKey(stack); + StackIdentity key = identity(stack); MutableEntry entry = grouped.computeIfAbsent(key, ignored -> new MutableEntry(key.stack)); entry.total = saturatedAdd(entry.total, stack.getCount()); } @@ -84,10 +84,14 @@ public final class StorageTerminalInventory { public record Entry(ItemStack stack, int total) { public Entry { stack = stack == null || stack.isEmpty() ? ItemStack.EMPTY : stack.copyWithCount(1); - total = stack.isEmpty() ? 0 : Math.max(1, total); + total = stack.isEmpty() ? 0 : Math.max(0, total); } } + static StackIdentity identity(ItemStack stack) { + return new StackIdentity(stack); + } + private static final class MutableEntry { private final ItemStack stack; private int total; @@ -97,11 +101,11 @@ public final class StorageTerminalInventory { } } - private static final class StackKey { + static final class StackIdentity { private final ItemStack stack; private final int hash; - private StackKey(ItemStack stack) { + private StackIdentity(ItemStack stack) { this.stack = stack.copyWithCount(1); this.hash = ItemStack.hashItemAndComponents(this.stack); } @@ -113,7 +117,12 @@ public final class StorageTerminalInventory { @Override public boolean equals(Object other) { - return other instanceof StackKey key && ItemStack.isSameItemSameComponents(stack, key.stack); + return other instanceof StackIdentity identity + && ItemStack.isSameItemSameComponents(stack, identity.stack); + } + + ItemStack representative() { + return stack.copyWithCount(1); } } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java new file mode 100644 index 0000000..51e5208 --- /dev/null +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java @@ -0,0 +1,52 @@ +package fr.koka99cab.sanctuary26.redstoner.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.minecraft.world.item.ItemStack; + +/** Keeps terminal slot identities stable for the lifetime of one search layout. */ +final class StorageTerminalLayout { + private static final StorageTerminalInventory.Entry EMPTY_ENTRY = + new StorageTerminalInventory.Entry(ItemStack.EMPTY, 0); + private final List order = new ArrayList<>(); + private Map liveByIdentity = Map.of(); + + void update(List liveEntries, boolean resetOrder) { + Map live = + new LinkedHashMap<>(); + for (StorageTerminalInventory.Entry entry : liveEntries) { + live.put(StorageTerminalInventory.identity(entry.stack()), entry); + } + if (resetOrder) order.clear(); + Set known = new HashSet<>(order); + for (StorageTerminalInventory.StackIdentity identity : live.keySet()) { + if (known.add(identity)) order.add(identity); + } + liveByIdentity = Map.copyOf(live); + } + + int size() { + return order.size(); + } + + List page(int requestedPage) { + int start = StorageTerminalRules.pageStart(requestedPage, order.size()); + List entries = new ArrayList<>(StorageTerminalRules.PAGE_SIZE); + for (int slot = 0; slot < StorageTerminalRules.PAGE_SIZE; slot++) { + int orderedIndex = start + slot; + if (orderedIndex >= order.size()) { + entries.add(EMPTY_ENTRY); + continue; + } + StorageTerminalInventory.StackIdentity identity = order.get(orderedIndex); + StorageTerminalInventory.Entry live = liveByIdentity.get(identity); + entries.add(live == null + ? new StorageTerminalInventory.Entry(identity.representative(), 0) : live); + } + return List.copyOf(entries); + } +} 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 887dc95..839cfb9 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 @@ -21,10 +21,13 @@ import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; -/** A server-authoritative, read-only aggregate view over the connected physical storage. */ +/** A server-authoritative aggregate view over the connected physical storage. */ public final class StorageTerminalMenu extends AbstractContainerMenu { public static final int TERMINAL_SLOT_COUNT = StorageTerminalRules.PAGE_SIZE; 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 SEARCH_CODEPOINT_BUTTON_BASE = 1_000; private static final int PLAYER_INVENTORY_Y = 140; private static final int REFRESH_INTERVAL = 20; private static final double MAX_DISTANCE_SQUARED = 64.0D; @@ -43,7 +46,9 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { private final BlockPos terminalPos; private StorageTerminalNetwork.Discovery discovery = StorageTerminalNetwork.Discovery.error(StorageTerminalStatus.INVALID_TERMINAL); + private final StorageTerminalLayout layout = new StorageTerminalLayout(); private List entries = List.of(); + private String searchQuery = ""; private int refreshCooldown = REFRESH_INTERVAL; public StorageTerminalMenu(int containerId, Inventory inventory) { @@ -52,7 +57,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { public StorageTerminalMenu(int containerId, Inventory inventory, ServerPlayer player, BlockPos pos) { this(containerId, inventory, new SimpleContainerData(DATA_COUNT), player, pos.immutable()); - refresh(0); + refresh(0, true); } private StorageTerminalMenu(int containerId, Inventory inventory, ContainerData data, @@ -115,8 +120,25 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { public boolean clickMenuButton(Player player, int buttonId) { if (serverPlayer == null || player != serverPlayer || !stillValid(player)) return false; int requestedPage = buttonId - PAGE_BUTTON_BASE; - if (requestedPage < 0 || requestedPage >= StorageTerminalRules.MAX_PAGE_COUNT) return false; - refresh(requestedPage); + if (requestedPage >= 0 && requestedPage < StorageTerminalRules.MAX_PAGE_COUNT) { + refresh(requestedPage, false); + broadcastChanges(); + return true; + } + if (buttonId == SEARCH_CLEAR_BUTTON) { + searchQuery = ""; + } else if (buttonId == SEARCH_BACKSPACE_BUTTON) { + if (searchQuery.isEmpty()) return true; + searchQuery = searchQuery.substring(0, + searchQuery.offsetByCodePoints(searchQuery.length(), -1)); + } else { + int codePoint = buttonId - SEARCH_CODEPOINT_BUTTON_BASE; + if (!isSearchCodePoint(codePoint)) return false; + String appended = new String(Character.toChars(codePoint)); + if (searchQuery.length() + appended.length() > StorageTerminalRules.MAX_SEARCH_LENGTH) return false; + searchQuery += appended; + } + refresh(0, true); broadcastChanges(); return true; } @@ -156,7 +178,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { if (source.isEmpty()) sourceSlot.setByPlayer(ItemStack.EMPTY); else sourceSlot.setChanged(); sourceSlot.onTake(player, source); - refresh(page()); + refresh(page(), false); broadcastChanges(); return original; } @@ -171,7 +193,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { @Override public void broadcastChanges() { - if (serverPlayer != null && --refreshCooldown <= 0) refresh(page()); + if (serverPlayer != null && --refreshCooldown <= 0) refresh(page(), false); super.broadcastChanges(); } @@ -185,19 +207,27 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { return !(slot instanceof TerminalDisplaySlot) && super.canTakeItemForPickAll(stack, slot); } - private void refresh(int requestedPage) { + private void refresh(int requestedPage, boolean resetOrder) { if (serverPlayer == null || terminalPos == null) return; discovery = StorageTerminalNetwork.discover(serverPlayer, terminalPos); - StorageTerminalNetwork.Snapshot snapshot = StorageTerminalNetwork.snapshot(discovery, requestedPage); - entries = snapshot.entries(); - data.set(STATUS_DATA, snapshot.status().ordinal()); - data.set(PAGE_DATA, snapshot.page()); - data.set(PAGE_COUNT_DATA, snapshot.pageCount()); - data.set(CONTAINER_COUNT_DATA, snapshot.containerCount()); - data.set(TOTAL_ENTRIES_DATA, snapshot.totalEntries()); + List liveEntries = + StorageTerminalNetwork.filteredEntries(discovery, searchQuery); + layout.update(liveEntries, resetOrder); + + int page = discovery.status() == StorageTerminalStatus.READY + ? StorageTerminalRules.clampPage(requestedPage, layout.size()) : 0; + int pageCount = discovery.status() == StorageTerminalStatus.READY + ? StorageTerminalRules.pageCount(layout.size()) : 1; + entries = discovery.status() == StorageTerminalStatus.READY ? layout.page(page) : List.of(); + data.set(STATUS_DATA, discovery.status().ordinal()); + data.set(PAGE_DATA, page); + data.set(PAGE_COUNT_DATA, pageCount); + data.set(CONTAINER_COUNT_DATA, discovery.containers().size()); + data.set(TOTAL_ENTRIES_DATA, liveEntries.size()); for (int slot = 0; slot < TERMINAL_SLOT_COUNT; slot++) { StorageTerminalInventory.Entry entry = slot < entries.size() ? entries.get(slot) : null; - display.setItem(slot, entry == null ? ItemStack.EMPTY : entry.stack().copyWithCount(1)); + display.setItem(slot, entry == null || entry.total() <= 0 + ? ItemStack.EMPTY : entry.stack().copyWithCount(1)); int total = entry == null ? 0 : entry.total(); int base = TOTALS_DATA + slot * WORDS_PER_TOTAL; data.set(base, StorageTerminalRules.totalLowWord(total)); @@ -206,6 +236,12 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { refreshCooldown = REFRESH_INTERVAL; } + private static boolean isSearchCodePoint(int codePoint) { + return Character.isValidCodePoint(codePoint) + && !(codePoint >= Character.MIN_SURROGATE && codePoint <= Character.MAX_SURROGATE) + && !Character.isISOControl(codePoint); + } + private boolean ready() { if (serverPlayer == null || terminalPos == null) return false; discovery = StorageTerminalNetwork.discover(serverPlayer, terminalPos); @@ -215,12 +251,13 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { private void takeToCarried(int slot, boolean single) { if (!ready() || slot >= entries.size()) return; StorageTerminalInventory.Entry entry = entries.get(slot); + if (entry.stack().isEmpty() || entry.total() <= 0) return; int requested = single ? 1 : entry.stack().getMaxStackSize(); ItemStack extracted = StorageTerminalInventory.take(discovery.inventoryContainers(), entry.stack(), Math.min(entry.total(), requested)); if (extracted.isEmpty()) return; setCarried(extracted); - refresh(page()); + refresh(page(), false); broadcastChanges(); } @@ -233,13 +270,14 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { if (moved <= 0) return; carried.shrink(moved); if (carried.isEmpty()) setCarried(ItemStack.EMPTY); - refresh(page()); + refresh(page(), false); broadcastChanges(); } private ItemStack takeIntoPlayer(int slot) { if (!ready() || slot >= entries.size()) return ItemStack.EMPTY; StorageTerminalInventory.Entry entry = entries.get(slot); + if (entry.stack().isEmpty() || entry.total() <= 0) return ItemStack.EMPTY; int requested = Math.min(entry.total(), entry.stack().getMaxStackSize()); ItemStack extracted = StorageTerminalInventory.take(discovery.inventoryContainers(), entry.stack(), requested); if (extracted.isEmpty()) return ItemStack.EMPTY; @@ -247,7 +285,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { int inserted = insertPlayer(serverPlayer, extracted); if (!extracted.isEmpty()) StorageTerminalInventory.insert(discovery.inventoryContainers(), extracted); if (inserted <= 0) return ItemStack.EMPTY; - refresh(page()); + refresh(page(), false); broadcastChanges(); return original.copyWithCount(inserted); } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java index 9a9b3cc..51e961d 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java @@ -48,16 +48,11 @@ public final class StorageTerminalNetwork { return new Discovery(StorageTerminalStatus.READY, containers); } - public static Snapshot snapshot(Discovery discovery, int requestedPage) { - if (discovery.status() != StorageTerminalStatus.READY) { - return new Snapshot(discovery.status(), 0, 1, 0, 0, List.of()); - } - List all = StorageTerminalInventory.aggregate(discovery.containers()); - int page = StorageTerminalRules.clampPage(requestedPage, all.size()); - int start = StorageTerminalRules.pageStart(page, all.size()); - int end = Math.min(all.size(), start + StorageTerminalRules.PAGE_SIZE); - return new Snapshot(StorageTerminalStatus.READY, page, StorageTerminalRules.pageCount(all.size()), - discovery.containers().size(), all.size(), all.subList(start, end)); + public static List filteredEntries( + Discovery discovery, String searchQuery) { + if (discovery.status() != StorageTerminalStatus.READY) return List.of(); + return StorageTerminalSearch.filter( + StorageTerminalInventory.aggregate(discovery.containers()), searchQuery); } private static BaseContainerBlockEntity storageEntity(ServerLevel level, BlockPos pos) { @@ -81,12 +76,4 @@ public final class StorageTerminalNetwork { return containers; } } - - public record Snapshot(StorageTerminalStatus status, int page, int pageCount, int containerCount, - int totalEntries, List entries) { - public Snapshot { - status = status == null ? StorageTerminalStatus.INVALID_TERMINAL : status; - entries = entries == null ? List.of() : List.copyOf(entries); - } - } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java index 4352764..9229067 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java @@ -6,6 +6,7 @@ public final class StorageTerminalRules { public static final int SLOTS_PER_CONTAINER = 27; public static final int MAX_ENTRIES = MAX_CONTAINERS * SLOTS_PER_CONTAINER; public static final int MAX_PAGE_COUNT = MAX_ENTRIES / PAGE_SIZE; + public static final int MAX_SEARCH_LENGTH = 32; private StorageTerminalRules() { } @@ -34,4 +35,20 @@ public final class StorageTerminalRules { public static int totalFromWords(int low, int high) { return (low & 0xFFFF) | (high & 0xFFFF) << 16; } + + public static String compactCount(int count) { + int bounded = Math.max(0, count); + if (bounded < 1_000) return Integer.toString(bounded); + if (bounded < 10_000) return decimalUnit(bounded, 1_000, "K"); + if (bounded < 1_000_000) return (bounded / 1_000) + "K"; + if (bounded < 10_000_000) return decimalUnit(bounded, 1_000_000, "M"); + return (bounded / 1_000_000) + "M"; + } + + private static String decimalUnit(int count, int unit, String suffix) { + int tenths = count / (unit / 10); + int decimal = tenths % 10; + return decimal == 0 ? (tenths / 10) + suffix + : (tenths / 10) + "." + decimal + suffix; + } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java new file mode 100644 index 0000000..ff0d0bb --- /dev/null +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java @@ -0,0 +1,51 @@ +package fr.koka99cab.sanctuary26.redstoner.storage; + +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; + +/** Server-side matching for the bounded terminal search field. */ +public final class StorageTerminalSearch { + private static final Pattern COMBINING_MARKS = Pattern.compile("\\p{M}+"); + private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^\\p{L}\\p{N}]+"); + + private StorageTerminalSearch() { + } + + public static List filter( + List entries, String query) { + String normalizedQuery = normalize(query); + if (normalizedQuery.isEmpty()) return List.copyOf(entries); + String[] terms = normalizedQuery.split(" "); + List matches = new ArrayList<>(); + for (StorageTerminalInventory.Entry entry : entries) { + if (matches(entry.stack(), terms)) matches.add(entry); + } + return List.copyOf(matches); + } + + static String normalize(String value) { + if (value == null || value.isBlank()) return ""; + String withoutMarks = COMBINING_MARKS.matcher( + Normalizer.normalize(value, Normalizer.Form.NFD)).replaceAll(""); + return NON_ALPHANUMERIC.matcher(withoutMarks.toLowerCase(Locale.ROOT)) + .replaceAll(" ").strip(); + } + + private static boolean matches(ItemStack stack, String[] terms) { + StringBuilder searchable = new StringBuilder(stack.getHoverName().getString()); + Identifier id = BuiltInRegistries.ITEM.getKey(stack.getItem()); + if (id != null) searchable.append(' ').append(id); + searchable.append(' ').append(stack.getItem().getDescriptionId()); + String normalized = normalize(searchable.toString()); + for (String term : terms) { + if (!normalized.contains(term)) return false; + } + return true; + } +} 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 60fb008..7de24e6 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/en_us.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/en_us.json @@ -52,6 +52,8 @@ "tooltip.redstoner.particuleur": "Insert a catalyst, then power the block with redstone.", "block.redstoner.storage_terminal": "Storage Terminal", "screen.redstoner.storage_terminal.title": "Storage Terminal", + "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.error.invalid_terminal": "The terminal is no longer accessible.", "screen.redstoner.storage_terminal.error.disconnected": "No connected chest or barrel.", 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 77b1801..6d4cac0 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json @@ -52,6 +52,8 @@ "tooltip.redstoner.particuleur": "Insérez un catalyseur puis alimentez le bloc en redstone.", "block.redstoner.storage_terminal": "Terminal de stockage", "screen.redstoner.storage_terminal.title": "Terminal de stockage", + "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.error.invalid_terminal": "Le terminal n'est plus accessible.", "screen.redstoner.storage_terminal.error.disconnected": "Aucun coffre ou baril connecté.", 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 d6dbc2f..fec0373 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json @@ -52,6 +52,8 @@ "tooltip.redstoner.particuleur": "Вставьте катализатор и подайте сигнал редстоуна.", "block.redstoner.storage_terminal": "Терминал хранилища", "screen.redstoner.storage_terminal.title": "Терминал хранилища", + "screen.redstoner.storage_terminal.search": "Поиск…", + "screen.redstoner.storage_terminal.no_results": "Предметы не найдены.", "screen.redstoner.storage_terminal.summary": "%s/%s · блоков: %s · типов: %s", "screen.redstoner.storage_terminal.error.invalid_terminal": "Терминал больше недоступен.", "screen.redstoner.storage_terminal.error.disconnected": "Нет подключённых сундуков или бочек.", 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 e8a9863..0a47e17 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 @@ -25,6 +25,8 @@ public final class StorageTerminalSmoke { verifyTopology(); verifyPagination(); verifyAggregationAndTransfers(); + verifySearch(); + verifyStableLayout(); } private static void verifyTopology() { @@ -58,6 +60,12 @@ public final class StorageTerminalSmoke { (short) StorageTerminalRules.totalLowWord(networkMaximum), (short) StorageTerminalRules.totalHighWord(networkMaximum)) == networkMaximum, "A large aggregated quantity was truncated by menu data synchronization"); + require(StorageTerminalRules.compactCount(999).equals("999") + && StorageTerminalRules.compactCount(1_000).equals("1K") + && StorageTerminalRules.compactCount(1_250).equals("1.2K") + && StorageTerminalRules.compactCount(12_345).equals("12K") + && StorageTerminalRules.compactCount(1_250_000).equals("1.2M"), + "Terminal quantities were not compacted without overstating their value"); } private static void verifyAggregationAndTransfers() { @@ -98,6 +106,55 @@ public final class StorageTerminalSmoke { .findFirst().orElseThrow().total() == 6, "Terminal extraction did not update physical slots"); } + private static void verifySearch() { + SimpleContainer container = new SimpleContainer(4); + container.setItem(0, new ItemStack(stackable(Items.COBBLESTONE), 12)); + ItemStack accented = new ItemStack(stackable(Items.DIAMOND), 2); + accented.set(DataComponents.CUSTOM_NAME, Component.literal("Élément spécial")); + container.setItem(1, accented); + ItemStack russian = new ItemStack(stackable(Items.REDSTONE), 3); + russian.set(DataComponents.CUSTOM_NAME, Component.literal("Красный камень")); + container.setItem(2, russian); + + List entries = StorageTerminalInventory.aggregate(List.of(container)); + require(StorageTerminalSearch.filter(entries, "minecraft:cobblestone").size() == 1, + "Terminal search did not match a namespaced item id"); + require(StorageTerminalSearch.filter(entries, "cobble stone").size() == 1, + "Terminal search did not combine multiple terms"); + require(StorageTerminalSearch.filter(entries, "element special").size() == 1, + "Terminal search did not ignore accents in a custom name"); + require(StorageTerminalSearch.filter(entries, "красный камень").size() == 1, + "Terminal search did not preserve non-Latin keyboard input"); + require(StorageTerminalSearch.filter(entries, "missing").isEmpty(), + "Terminal search retained an unrelated item"); + } + + private static Holder stackable(Item item) { + return new Holder.Direct<>(item, DataComponentMap.builder() + .set(DataComponents.MAX_STACK_SIZE, 64).build()); + } + + private static void verifyStableLayout() { + StorageTerminalInventory.Entry stone = new StorageTerminalInventory.Entry( + new ItemStack(stackable(Items.STONE)), 64); + StorageTerminalInventory.Entry dirt = new StorageTerminalInventory.Entry( + new ItemStack(stackable(Items.DIRT)), 64); + StorageTerminalInventory.Entry diamond = new StorageTerminalInventory.Entry( + new ItemStack(stackable(Items.DIAMOND)), 8); + StorageTerminalLayout layout = new StorageTerminalLayout(); + layout.update(List.of(stone, dirt), true); + layout.update(List.of(dirt, diamond), false); + List page = layout.page(0); + require(page.get(0).total() == 0 && ItemStack.isSameItemSameComponents(page.get(0).stack(), stone.stack()), + "An exhausted terminal resource moved another item under the cursor"); + require(ItemStack.isSameItemSameComponents(page.get(1).stack(), dirt.stack()) + && ItemStack.isSameItemSameComponents(page.get(2).stack(), diamond.stack()), + "A terminal refresh did not preserve existing slots or append a new resource"); + layout.update(List.of(dirt, diamond), true); + require(ItemStack.isSameItemSameComponents(layout.page(0).get(0).stack(), dirt.stack()), + "A deliberate terminal layout reset did not compact the visible resources"); + } + private static void require(boolean condition, String message) { if (!condition) throw new IllegalStateException(message); } -- 2.54.0 From fd528abb9da61a1ceb476544b431a31d0e65ed05 Mon Sep 17 00:00:00 2001 From: koka Date: Sun, 30 Aug 2026 08:43:17 +0200 Subject: [PATCH 2/2] feat(redstoner): add storage terminal keyboard search --- build.gradle | 85 +++++++++++++++++-- gradle.properties | 2 +- .../client/gui/StorageTerminalScreen.java | 59 +++++++++++-- .../storage/StorageTerminalInventory.java | 21 +++-- .../storage/StorageTerminalLayout.java | 52 ++++++++++++ .../storage/StorageTerminalMenu.java | 74 ++++++++++++---- .../storage/StorageTerminalNetwork.java | 23 ++--- .../storage/StorageTerminalRules.java | 17 ++++ .../storage/StorageTerminalSearch.java | 51 +++++++++++ .../assets/redstoner/lang/en_us.json | 2 + .../assets/redstoner/lang/fr_fr.json | 2 + .../assets/redstoner/lang/ru_ru.json | 2 + .../storage/StorageTerminalSmoke.java | 57 +++++++++++++ 13 files changed, 391 insertions(+), 56 deletions(-) create mode 100644 redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java create mode 100644 redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java diff --git a/build.gradle b/build.gradle index daa0b40..49778a6 100644 --- a/build.gradle +++ b/build.gradle @@ -3489,7 +3489,7 @@ tasks.register("verifyMirrorLavenderDrawerRelease") { def drawerRecipe = file("redstoner/src/main/resources/data/redstoner/recipe/drawer.json") if (rootProject.pack_version != "26.2.0-alpha.221" || rootProject.anotherworld_version != "0.0.0-alpha.55" - || rootProject.redstoner_version != "0.0.0-alpha.12" + || rootProject.redstoner_version != "0.0.0-alpha.13" || 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"] @@ -4086,6 +4086,8 @@ project(":redstoner") { "screen.redstoner.personal_computer.shutdown", "message.redstoner.personal_computer.atlas_marker_exists", "screen.redstoner.storage_terminal.title", + "screen.redstoner.storage_terminal.search", + "screen.redstoner.storage_terminal.no_results", "screen.redstoner.storage_terminal.summary", "screen.redstoner.storage_terminal.error.invalid_terminal", "screen.redstoner.storage_terminal.error.disconnected", @@ -4103,7 +4105,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.12" + if (project.version.toString() != "0.0.0-alpha.13" || manifest.custom?.sanctuary26?.data_version != 3 || manifest.custom?.sanctuary26?.network_protocol != 7 || terminalMigration.source?.pack_version != "26.2.0-alpha.217" @@ -4144,7 +4146,7 @@ project(":redstoner") { P: "anotherworld:silver_plate", G: "minecraft:tinted_glass", C: "redstoner:controller", T: "anotherworld:titanium_ingot"] || terminalRecipe.result != [id: "redstoner:storage_terminal", count: 1]) { - throw new GradleException("Red-Stoner alpha.12 Storage Terminal menu contract is incomplete") + throw new GradleException("Red-Stoner alpha.13 Storage Terminal contract is incomplete") } if (file("src/main/resources/data/redstoner/recipe/personal_computer.json").exists() || file("src/main/resources/data/redstoner/recipe/drawer.json").exists() @@ -6443,7 +6445,10 @@ tasks.register("verifyAlpha218Release") { "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.221" - || currentModules.any { id, version -> rootProject.property("${id}_version") != version } + || rootProject.redstoner_version != "0.0.0-alpha.13" + || currentModules.findAll { id, version -> id != "redstoner" }.any { + id, version -> rootProject.property("${id}_version") != version + } || migration.source?.pack_version != "26.2.0-alpha.217" || migration.source?.modules != sourceModules || migration.target?.pack_version != "26.2.0-alpha.218" @@ -6620,7 +6625,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.221" - || rootProject.redstoner_version != "0.0.0-alpha.12" + || rootProject.redstoner_version != "0.0.0-alpha.13" || 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" @@ -6775,6 +6780,75 @@ tasks.register("verifyAlpha221Release") { } } +tasks.register("verifyStorageTerminalSearchFeature") { + group = "verification" + description = "Checks the unreleased Storage Terminal keyboard-search feature." + inputs.file(file("pack/release.json")) + inputs.file(file("pack/prism/modrinth.index.json")) + inputs.file(file("pack/packwiz/pack.toml")) + inputs.file(file("pack/packwiz/index.toml")) + inputs.file(file("redstoner/src/main/resources/fabric.mod.json")) + 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/storage/StorageTerminalNetwork.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java")) + inputs.file(file("redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java")) + inputs.file(file("redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java")) + doLast { + def release = new JsonSlurper().parse(file("pack/release.json")) + def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) + def packwiz = file("pack/packwiz/pack.toml").text + def packwizIndex = file("pack/packwiz/index.toml").text + def manifest = new JsonSlurper().parse(file("redstoner/src/main/resources/fabric.mod.json")) + def menu = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalMenu.java").text + def network = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java").text + def rules = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java").text + def layout = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java").text + def search = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java").text + def screen = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java").text + def networking = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java").text + def smoke = file( + "redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java").text + if (rootProject.pack_version != "26.2.0-alpha.221" + || rootProject.redstoner_version != "0.0.0-alpha.13" + || manifest.custom?.sanctuary26?.data_version != 3 + || manifest.custom?.sanctuary26?.network_protocol != 7 + || !rules.contains("MAX_SEARCH_LENGTH = 32") + || !rules.contains("compactCount(int count)") + || !layout.contains("Keeps terminal slot identities stable") + || !layout.contains("new StorageTerminalInventory.Entry(identity.representative(), 0)") + || !menu.contains("SEARCH_CODEPOINT_BUTTON_BASE") + || !menu.contains("isSearchCodePoint") + || !network.contains("StorageTerminalSearch.filter") + || !search.contains("Normalizer.Form.NFD") + || !search.contains("BuiltInRegistries.ITEM.getKey") + || !screen.contains("new EditBox") + || !screen.contains("setInitialFocus(searchBox)") + || !screen.contains("searchBox.setResponder(this::searchChanged)") + || !smoke.contains("verifySearch()") + || !smoke.contains("verifyStableLayout()") + || !networking.contains("PROTOCOL_VERSION = 7") + || networking.contains("storage_terminal_search") + || release.pack_version != "26.2.0-alpha.221" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.221.mrpack" + || prism.versionId != "26.2.0-alpha.221" + || !packwiz.contains('version = "26.2.0-alpha.221"') + || !packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.12.jar"') + || packwizIndex.contains('file = "mods/redstoner-0.0.0-alpha.13.jar"')) { + throw new GradleException("The unreleased Storage Terminal keyboard-search feature is incomplete") + } + } +} + tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyAlpha213Release") dependsOn tasks.named("verifyAlpha214Release") @@ -6785,6 +6859,7 @@ tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyAlpha219Release") dependsOn tasks.named("verifyAlpha220Release") dependsOn tasks.named("verifyAlpha221Release") + dependsOn tasks.named("verifyStorageTerminalSearchFeature") } tasks.named("check") { diff --git a/gradle.properties b/gradle.properties index 4347b3c..8ff1633 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.12 +redstoner_version=0.0.0-alpha.13 redstoner_lifecycle=active anotherworld_version=0.0.0-alpha.55 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 f9a94a9..ccf9f8c 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 @@ -1,12 +1,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.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.EditBox; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; @@ -24,6 +27,9 @@ public final class StorageTerminalScreen extends AbstractContainerScreen 1) { - String label = compactCount(total); + String label = StorageTerminalRules.compactCount(total); graphics.text(font, label, slot.x + 17 - font.width(label), slot.y + 9, 0xFFFFFFFF, true); } @@ -111,6 +125,15 @@ public final class StorageTerminalScreen extends AbstractContainerScreen sendMenuButton( + StorageTerminalMenu.SEARCH_CODEPOINT_BUTTON_BASE + codePoint)); + } + sentSearchValue = value; + } + + private void sendMenuButton(int buttonId) { + if (minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, buttonId); + } + } + private void extractScrollBar(GuiGraphicsExtractor graphics) { int x = leftPos + SCROLL_X; int y = topPos + TERMINAL_TOP; @@ -137,9 +185,4 @@ public final class StorageTerminalScreen extends AbstractContainerScreen= 1_000_000) return (count / 1_000_000) + "m"; - if (count >= 10_000) return (count / 1_000) + "k"; - return Integer.toString(count); - } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java index 7f3f112..06a1550 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalInventory.java @@ -12,12 +12,12 @@ public final class StorageTerminalInventory { } public static List aggregate(List containers) { - Map grouped = new LinkedHashMap<>(); + Map grouped = new LinkedHashMap<>(); for (Container container : containers) { for (int slot = 0; slot < container.getContainerSize(); slot++) { ItemStack stack = container.getItem(slot); if (stack.isEmpty()) continue; - StackKey key = new StackKey(stack); + StackIdentity key = identity(stack); MutableEntry entry = grouped.computeIfAbsent(key, ignored -> new MutableEntry(key.stack)); entry.total = saturatedAdd(entry.total, stack.getCount()); } @@ -84,10 +84,14 @@ public final class StorageTerminalInventory { public record Entry(ItemStack stack, int total) { public Entry { stack = stack == null || stack.isEmpty() ? ItemStack.EMPTY : stack.copyWithCount(1); - total = stack.isEmpty() ? 0 : Math.max(1, total); + total = stack.isEmpty() ? 0 : Math.max(0, total); } } + static StackIdentity identity(ItemStack stack) { + return new StackIdentity(stack); + } + private static final class MutableEntry { private final ItemStack stack; private int total; @@ -97,11 +101,11 @@ public final class StorageTerminalInventory { } } - private static final class StackKey { + static final class StackIdentity { private final ItemStack stack; private final int hash; - private StackKey(ItemStack stack) { + private StackIdentity(ItemStack stack) { this.stack = stack.copyWithCount(1); this.hash = ItemStack.hashItemAndComponents(this.stack); } @@ -113,7 +117,12 @@ public final class StorageTerminalInventory { @Override public boolean equals(Object other) { - return other instanceof StackKey key && ItemStack.isSameItemSameComponents(stack, key.stack); + return other instanceof StackIdentity identity + && ItemStack.isSameItemSameComponents(stack, identity.stack); + } + + ItemStack representative() { + return stack.copyWithCount(1); } } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java new file mode 100644 index 0000000..51e5208 --- /dev/null +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalLayout.java @@ -0,0 +1,52 @@ +package fr.koka99cab.sanctuary26.redstoner.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import net.minecraft.world.item.ItemStack; + +/** Keeps terminal slot identities stable for the lifetime of one search layout. */ +final class StorageTerminalLayout { + private static final StorageTerminalInventory.Entry EMPTY_ENTRY = + new StorageTerminalInventory.Entry(ItemStack.EMPTY, 0); + private final List order = new ArrayList<>(); + private Map liveByIdentity = Map.of(); + + void update(List liveEntries, boolean resetOrder) { + Map live = + new LinkedHashMap<>(); + for (StorageTerminalInventory.Entry entry : liveEntries) { + live.put(StorageTerminalInventory.identity(entry.stack()), entry); + } + if (resetOrder) order.clear(); + Set known = new HashSet<>(order); + for (StorageTerminalInventory.StackIdentity identity : live.keySet()) { + if (known.add(identity)) order.add(identity); + } + liveByIdentity = Map.copyOf(live); + } + + int size() { + return order.size(); + } + + List page(int requestedPage) { + int start = StorageTerminalRules.pageStart(requestedPage, order.size()); + List entries = new ArrayList<>(StorageTerminalRules.PAGE_SIZE); + for (int slot = 0; slot < StorageTerminalRules.PAGE_SIZE; slot++) { + int orderedIndex = start + slot; + if (orderedIndex >= order.size()) { + entries.add(EMPTY_ENTRY); + continue; + } + StorageTerminalInventory.StackIdentity identity = order.get(orderedIndex); + StorageTerminalInventory.Entry live = liveByIdentity.get(identity); + entries.add(live == null + ? new StorageTerminalInventory.Entry(identity.representative(), 0) : live); + } + return List.copyOf(entries); + } +} 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 887dc95..839cfb9 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 @@ -21,10 +21,13 @@ import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; -/** A server-authoritative, read-only aggregate view over the connected physical storage. */ +/** A server-authoritative aggregate view over the connected physical storage. */ public final class StorageTerminalMenu extends AbstractContainerMenu { public static final int TERMINAL_SLOT_COUNT = StorageTerminalRules.PAGE_SIZE; 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 SEARCH_CODEPOINT_BUTTON_BASE = 1_000; private static final int PLAYER_INVENTORY_Y = 140; private static final int REFRESH_INTERVAL = 20; private static final double MAX_DISTANCE_SQUARED = 64.0D; @@ -43,7 +46,9 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { private final BlockPos terminalPos; private StorageTerminalNetwork.Discovery discovery = StorageTerminalNetwork.Discovery.error(StorageTerminalStatus.INVALID_TERMINAL); + private final StorageTerminalLayout layout = new StorageTerminalLayout(); private List entries = List.of(); + private String searchQuery = ""; private int refreshCooldown = REFRESH_INTERVAL; public StorageTerminalMenu(int containerId, Inventory inventory) { @@ -52,7 +57,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { public StorageTerminalMenu(int containerId, Inventory inventory, ServerPlayer player, BlockPos pos) { this(containerId, inventory, new SimpleContainerData(DATA_COUNT), player, pos.immutable()); - refresh(0); + refresh(0, true); } private StorageTerminalMenu(int containerId, Inventory inventory, ContainerData data, @@ -115,8 +120,25 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { public boolean clickMenuButton(Player player, int buttonId) { if (serverPlayer == null || player != serverPlayer || !stillValid(player)) return false; int requestedPage = buttonId - PAGE_BUTTON_BASE; - if (requestedPage < 0 || requestedPage >= StorageTerminalRules.MAX_PAGE_COUNT) return false; - refresh(requestedPage); + if (requestedPage >= 0 && requestedPage < StorageTerminalRules.MAX_PAGE_COUNT) { + refresh(requestedPage, false); + broadcastChanges(); + return true; + } + if (buttonId == SEARCH_CLEAR_BUTTON) { + searchQuery = ""; + } else if (buttonId == SEARCH_BACKSPACE_BUTTON) { + if (searchQuery.isEmpty()) return true; + searchQuery = searchQuery.substring(0, + searchQuery.offsetByCodePoints(searchQuery.length(), -1)); + } else { + int codePoint = buttonId - SEARCH_CODEPOINT_BUTTON_BASE; + if (!isSearchCodePoint(codePoint)) return false; + String appended = new String(Character.toChars(codePoint)); + if (searchQuery.length() + appended.length() > StorageTerminalRules.MAX_SEARCH_LENGTH) return false; + searchQuery += appended; + } + refresh(0, true); broadcastChanges(); return true; } @@ -156,7 +178,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { if (source.isEmpty()) sourceSlot.setByPlayer(ItemStack.EMPTY); else sourceSlot.setChanged(); sourceSlot.onTake(player, source); - refresh(page()); + refresh(page(), false); broadcastChanges(); return original; } @@ -171,7 +193,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { @Override public void broadcastChanges() { - if (serverPlayer != null && --refreshCooldown <= 0) refresh(page()); + if (serverPlayer != null && --refreshCooldown <= 0) refresh(page(), false); super.broadcastChanges(); } @@ -185,19 +207,27 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { return !(slot instanceof TerminalDisplaySlot) && super.canTakeItemForPickAll(stack, slot); } - private void refresh(int requestedPage) { + private void refresh(int requestedPage, boolean resetOrder) { if (serverPlayer == null || terminalPos == null) return; discovery = StorageTerminalNetwork.discover(serverPlayer, terminalPos); - StorageTerminalNetwork.Snapshot snapshot = StorageTerminalNetwork.snapshot(discovery, requestedPage); - entries = snapshot.entries(); - data.set(STATUS_DATA, snapshot.status().ordinal()); - data.set(PAGE_DATA, snapshot.page()); - data.set(PAGE_COUNT_DATA, snapshot.pageCount()); - data.set(CONTAINER_COUNT_DATA, snapshot.containerCount()); - data.set(TOTAL_ENTRIES_DATA, snapshot.totalEntries()); + List liveEntries = + StorageTerminalNetwork.filteredEntries(discovery, searchQuery); + layout.update(liveEntries, resetOrder); + + int page = discovery.status() == StorageTerminalStatus.READY + ? StorageTerminalRules.clampPage(requestedPage, layout.size()) : 0; + int pageCount = discovery.status() == StorageTerminalStatus.READY + ? StorageTerminalRules.pageCount(layout.size()) : 1; + entries = discovery.status() == StorageTerminalStatus.READY ? layout.page(page) : List.of(); + data.set(STATUS_DATA, discovery.status().ordinal()); + data.set(PAGE_DATA, page); + data.set(PAGE_COUNT_DATA, pageCount); + data.set(CONTAINER_COUNT_DATA, discovery.containers().size()); + data.set(TOTAL_ENTRIES_DATA, liveEntries.size()); for (int slot = 0; slot < TERMINAL_SLOT_COUNT; slot++) { StorageTerminalInventory.Entry entry = slot < entries.size() ? entries.get(slot) : null; - display.setItem(slot, entry == null ? ItemStack.EMPTY : entry.stack().copyWithCount(1)); + display.setItem(slot, entry == null || entry.total() <= 0 + ? ItemStack.EMPTY : entry.stack().copyWithCount(1)); int total = entry == null ? 0 : entry.total(); int base = TOTALS_DATA + slot * WORDS_PER_TOTAL; data.set(base, StorageTerminalRules.totalLowWord(total)); @@ -206,6 +236,12 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { refreshCooldown = REFRESH_INTERVAL; } + private static boolean isSearchCodePoint(int codePoint) { + return Character.isValidCodePoint(codePoint) + && !(codePoint >= Character.MIN_SURROGATE && codePoint <= Character.MAX_SURROGATE) + && !Character.isISOControl(codePoint); + } + private boolean ready() { if (serverPlayer == null || terminalPos == null) return false; discovery = StorageTerminalNetwork.discover(serverPlayer, terminalPos); @@ -215,12 +251,13 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { private void takeToCarried(int slot, boolean single) { if (!ready() || slot >= entries.size()) return; StorageTerminalInventory.Entry entry = entries.get(slot); + if (entry.stack().isEmpty() || entry.total() <= 0) return; int requested = single ? 1 : entry.stack().getMaxStackSize(); ItemStack extracted = StorageTerminalInventory.take(discovery.inventoryContainers(), entry.stack(), Math.min(entry.total(), requested)); if (extracted.isEmpty()) return; setCarried(extracted); - refresh(page()); + refresh(page(), false); broadcastChanges(); } @@ -233,13 +270,14 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { if (moved <= 0) return; carried.shrink(moved); if (carried.isEmpty()) setCarried(ItemStack.EMPTY); - refresh(page()); + refresh(page(), false); broadcastChanges(); } private ItemStack takeIntoPlayer(int slot) { if (!ready() || slot >= entries.size()) return ItemStack.EMPTY; StorageTerminalInventory.Entry entry = entries.get(slot); + if (entry.stack().isEmpty() || entry.total() <= 0) return ItemStack.EMPTY; int requested = Math.min(entry.total(), entry.stack().getMaxStackSize()); ItemStack extracted = StorageTerminalInventory.take(discovery.inventoryContainers(), entry.stack(), requested); if (extracted.isEmpty()) return ItemStack.EMPTY; @@ -247,7 +285,7 @@ public final class StorageTerminalMenu extends AbstractContainerMenu { int inserted = insertPlayer(serverPlayer, extracted); if (!extracted.isEmpty()) StorageTerminalInventory.insert(discovery.inventoryContainers(), extracted); if (inserted <= 0) return ItemStack.EMPTY; - refresh(page()); + refresh(page(), false); broadcastChanges(); return original.copyWithCount(inserted); } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java index 9a9b3cc..51e961d 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalNetwork.java @@ -48,16 +48,11 @@ public final class StorageTerminalNetwork { return new Discovery(StorageTerminalStatus.READY, containers); } - public static Snapshot snapshot(Discovery discovery, int requestedPage) { - if (discovery.status() != StorageTerminalStatus.READY) { - return new Snapshot(discovery.status(), 0, 1, 0, 0, List.of()); - } - List all = StorageTerminalInventory.aggregate(discovery.containers()); - int page = StorageTerminalRules.clampPage(requestedPage, all.size()); - int start = StorageTerminalRules.pageStart(page, all.size()); - int end = Math.min(all.size(), start + StorageTerminalRules.PAGE_SIZE); - return new Snapshot(StorageTerminalStatus.READY, page, StorageTerminalRules.pageCount(all.size()), - discovery.containers().size(), all.size(), all.subList(start, end)); + public static List filteredEntries( + Discovery discovery, String searchQuery) { + if (discovery.status() != StorageTerminalStatus.READY) return List.of(); + return StorageTerminalSearch.filter( + StorageTerminalInventory.aggregate(discovery.containers()), searchQuery); } private static BaseContainerBlockEntity storageEntity(ServerLevel level, BlockPos pos) { @@ -81,12 +76,4 @@ public final class StorageTerminalNetwork { return containers; } } - - public record Snapshot(StorageTerminalStatus status, int page, int pageCount, int containerCount, - int totalEntries, List entries) { - public Snapshot { - status = status == null ? StorageTerminalStatus.INVALID_TERMINAL : status; - entries = entries == null ? List.of() : List.copyOf(entries); - } - } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java index 4352764..9229067 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalRules.java @@ -6,6 +6,7 @@ public final class StorageTerminalRules { public static final int SLOTS_PER_CONTAINER = 27; public static final int MAX_ENTRIES = MAX_CONTAINERS * SLOTS_PER_CONTAINER; public static final int MAX_PAGE_COUNT = MAX_ENTRIES / PAGE_SIZE; + public static final int MAX_SEARCH_LENGTH = 32; private StorageTerminalRules() { } @@ -34,4 +35,20 @@ public final class StorageTerminalRules { public static int totalFromWords(int low, int high) { return (low & 0xFFFF) | (high & 0xFFFF) << 16; } + + public static String compactCount(int count) { + int bounded = Math.max(0, count); + if (bounded < 1_000) return Integer.toString(bounded); + if (bounded < 10_000) return decimalUnit(bounded, 1_000, "K"); + if (bounded < 1_000_000) return (bounded / 1_000) + "K"; + if (bounded < 10_000_000) return decimalUnit(bounded, 1_000_000, "M"); + return (bounded / 1_000_000) + "M"; + } + + private static String decimalUnit(int count, int unit, String suffix) { + int tenths = count / (unit / 10); + int decimal = tenths % 10; + return decimal == 0 ? (tenths / 10) + suffix + : (tenths / 10) + "." + decimal + suffix; + } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java new file mode 100644 index 0000000..ff0d0bb --- /dev/null +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSearch.java @@ -0,0 +1,51 @@ +package fr.koka99cab.sanctuary26.redstoner.storage; + +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; + +/** Server-side matching for the bounded terminal search field. */ +public final class StorageTerminalSearch { + private static final Pattern COMBINING_MARKS = Pattern.compile("\\p{M}+"); + private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^\\p{L}\\p{N}]+"); + + private StorageTerminalSearch() { + } + + public static List filter( + List entries, String query) { + String normalizedQuery = normalize(query); + if (normalizedQuery.isEmpty()) return List.copyOf(entries); + String[] terms = normalizedQuery.split(" "); + List matches = new ArrayList<>(); + for (StorageTerminalInventory.Entry entry : entries) { + if (matches(entry.stack(), terms)) matches.add(entry); + } + return List.copyOf(matches); + } + + static String normalize(String value) { + if (value == null || value.isBlank()) return ""; + String withoutMarks = COMBINING_MARKS.matcher( + Normalizer.normalize(value, Normalizer.Form.NFD)).replaceAll(""); + return NON_ALPHANUMERIC.matcher(withoutMarks.toLowerCase(Locale.ROOT)) + .replaceAll(" ").strip(); + } + + private static boolean matches(ItemStack stack, String[] terms) { + StringBuilder searchable = new StringBuilder(stack.getHoverName().getString()); + Identifier id = BuiltInRegistries.ITEM.getKey(stack.getItem()); + if (id != null) searchable.append(' ').append(id); + searchable.append(' ').append(stack.getItem().getDescriptionId()); + String normalized = normalize(searchable.toString()); + for (String term : terms) { + if (!normalized.contains(term)) return false; + } + return true; + } +} 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 60fb008..7de24e6 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/en_us.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/en_us.json @@ -52,6 +52,8 @@ "tooltip.redstoner.particuleur": "Insert a catalyst, then power the block with redstone.", "block.redstoner.storage_terminal": "Storage Terminal", "screen.redstoner.storage_terminal.title": "Storage Terminal", + "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.error.invalid_terminal": "The terminal is no longer accessible.", "screen.redstoner.storage_terminal.error.disconnected": "No connected chest or barrel.", 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 77b1801..6d4cac0 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json @@ -52,6 +52,8 @@ "tooltip.redstoner.particuleur": "Insérez un catalyseur puis alimentez le bloc en redstone.", "block.redstoner.storage_terminal": "Terminal de stockage", "screen.redstoner.storage_terminal.title": "Terminal de stockage", + "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.error.invalid_terminal": "Le terminal n'est plus accessible.", "screen.redstoner.storage_terminal.error.disconnected": "Aucun coffre ou baril connecté.", 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 d6dbc2f..fec0373 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json @@ -52,6 +52,8 @@ "tooltip.redstoner.particuleur": "Вставьте катализатор и подайте сигнал редстоуна.", "block.redstoner.storage_terminal": "Терминал хранилища", "screen.redstoner.storage_terminal.title": "Терминал хранилища", + "screen.redstoner.storage_terminal.search": "Поиск…", + "screen.redstoner.storage_terminal.no_results": "Предметы не найдены.", "screen.redstoner.storage_terminal.summary": "%s/%s · блоков: %s · типов: %s", "screen.redstoner.storage_terminal.error.invalid_terminal": "Терминал больше недоступен.", "screen.redstoner.storage_terminal.error.disconnected": "Нет подключённых сундуков или бочек.", 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 e8a9863..0a47e17 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 @@ -25,6 +25,8 @@ public final class StorageTerminalSmoke { verifyTopology(); verifyPagination(); verifyAggregationAndTransfers(); + verifySearch(); + verifyStableLayout(); } private static void verifyTopology() { @@ -58,6 +60,12 @@ public final class StorageTerminalSmoke { (short) StorageTerminalRules.totalLowWord(networkMaximum), (short) StorageTerminalRules.totalHighWord(networkMaximum)) == networkMaximum, "A large aggregated quantity was truncated by menu data synchronization"); + require(StorageTerminalRules.compactCount(999).equals("999") + && StorageTerminalRules.compactCount(1_000).equals("1K") + && StorageTerminalRules.compactCount(1_250).equals("1.2K") + && StorageTerminalRules.compactCount(12_345).equals("12K") + && StorageTerminalRules.compactCount(1_250_000).equals("1.2M"), + "Terminal quantities were not compacted without overstating their value"); } private static void verifyAggregationAndTransfers() { @@ -98,6 +106,55 @@ public final class StorageTerminalSmoke { .findFirst().orElseThrow().total() == 6, "Terminal extraction did not update physical slots"); } + private static void verifySearch() { + SimpleContainer container = new SimpleContainer(4); + container.setItem(0, new ItemStack(stackable(Items.COBBLESTONE), 12)); + ItemStack accented = new ItemStack(stackable(Items.DIAMOND), 2); + accented.set(DataComponents.CUSTOM_NAME, Component.literal("Élément spécial")); + container.setItem(1, accented); + ItemStack russian = new ItemStack(stackable(Items.REDSTONE), 3); + russian.set(DataComponents.CUSTOM_NAME, Component.literal("Красный камень")); + container.setItem(2, russian); + + List entries = StorageTerminalInventory.aggregate(List.of(container)); + require(StorageTerminalSearch.filter(entries, "minecraft:cobblestone").size() == 1, + "Terminal search did not match a namespaced item id"); + require(StorageTerminalSearch.filter(entries, "cobble stone").size() == 1, + "Terminal search did not combine multiple terms"); + require(StorageTerminalSearch.filter(entries, "element special").size() == 1, + "Terminal search did not ignore accents in a custom name"); + require(StorageTerminalSearch.filter(entries, "красный камень").size() == 1, + "Terminal search did not preserve non-Latin keyboard input"); + require(StorageTerminalSearch.filter(entries, "missing").isEmpty(), + "Terminal search retained an unrelated item"); + } + + private static Holder stackable(Item item) { + return new Holder.Direct<>(item, DataComponentMap.builder() + .set(DataComponents.MAX_STACK_SIZE, 64).build()); + } + + private static void verifyStableLayout() { + StorageTerminalInventory.Entry stone = new StorageTerminalInventory.Entry( + new ItemStack(stackable(Items.STONE)), 64); + StorageTerminalInventory.Entry dirt = new StorageTerminalInventory.Entry( + new ItemStack(stackable(Items.DIRT)), 64); + StorageTerminalInventory.Entry diamond = new StorageTerminalInventory.Entry( + new ItemStack(stackable(Items.DIAMOND)), 8); + StorageTerminalLayout layout = new StorageTerminalLayout(); + layout.update(List.of(stone, dirt), true); + layout.update(List.of(dirt, diamond), false); + List page = layout.page(0); + require(page.get(0).total() == 0 && ItemStack.isSameItemSameComponents(page.get(0).stack(), stone.stack()), + "An exhausted terminal resource moved another item under the cursor"); + require(ItemStack.isSameItemSameComponents(page.get(1).stack(), dirt.stack()) + && ItemStack.isSameItemSameComponents(page.get(2).stack(), diamond.stack()), + "A terminal refresh did not preserve existing slots or append a new resource"); + layout.update(List.of(dirt, diamond), true); + require(ItemStack.isSameItemSameComponents(layout.page(0).get(0).stack(), dirt.stack()), + "A deliberate terminal layout reset did not compact the visible resources"); + } + private static void require(boolean condition, String message) { if (!condition) throw new IllegalStateException(message); } -- 2.54.0