From 5840621acf4caca076024821f41f7d30a76fdba3 Mon Sep 17 00:00:00 2001 From: koka Date: Thu, 3 Sep 2026 01:35:09 +0200 Subject: [PATCH] fix: correct alpha231 cape terminal and trophy panel --- CHANGELOG.md | 10 ++ README.md | 2 + build.gradle | 152 +++++++++++------- gradle.properties | 4 +- ...pha231-follow-up-cape-terminal-trophy.json | 36 +++++ .../block/entity/TrophyPanelBlockEntity.java | 18 ++- .../client/gui/StorageTerminalScreen.java | 8 +- .../TrophyPanelBlockEntityRenderer.java | 25 +-- .../state/TrophyPanelRenderState.java | 11 +- .../network/RedStonerNetworking.java | 2 +- .../storage/StorageTerminalKeyRules.java | 10 ++ .../redstoner/trophy/TrophyPanelRules.java | 26 +++ .../assets/redstoner/lang/en_us.json | 2 + .../assets/redstoner/lang/fr_fr.json | 2 + .../assets/redstoner/lang/ru_ru.json | 2 + redstoner/src/main/resources/fabric.mod.json | 2 +- .../storage/StorageTerminalSmoke.java | 10 ++ .../trophy/TrophyPanelRulesSmoke.java | 12 ++ sanctuary/build.gradle | 46 ++++-- .../api/ranking/RankingSnapshot.java | 4 +- .../api/ranking/SanctuaryRankingApi.java | 6 + .../api/ranking/VanillaRankingMetric.java | 27 ++++ .../client/SanctuaryBannerCapeLayer.java | 28 +++- .../assets/sanctuary/lang/en_us.json | 1 + .../assets/sanctuary/lang/fr_fr.json | 1 + .../assets/sanctuary/lang/ru_ru.json | 1 + .../ranking/SanctuaryRankingModelSmoke.java | 26 +++ .../client/SanctuaryBannerCapeModelSmoke.java | 80 +++++++++ 28 files changed, 443 insertions(+), 111 deletions(-) create mode 100644 pack/migrations/alpha231-follow-up-cape-terminal-trophy.json create mode 100644 redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalKeyRules.java create mode 100644 sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeModelSmoke.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0115e..c6a12c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ 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. +## Non publié + +Modules modifiés : **Sanctuary `0.0.0-alpha.138`** et **Red-Stoner `0.0.0-alpha.19`**. + +- remplace le volume de la cape à motifs par les deux faces opposées de l’îlot UV exact du bouclier, redimensionnées aux proportions 10 × 16 de la cape, afin de corriger la projection et le culling sans afficher le manche ni l’épaisseur du bouclier ; +- laisse la touche d’inventaire fermer le terminal tant que la recherche n’est pas focalisée, mais la traite comme du texte dans le champ de recherche afin que la lettre `E` ne ferme plus l’écran ; +- utilise les huit lignes du panneau de trophées pour afficher le titre et jusqu’à sept rangs, avec une page de noms suivie d’une page de valeurs pour chaque statistique afin que les longues durées de jeu restent lisibles ; +- fait configurer par un œuf d’apparition le classement vanilla des créatures correspondantes tuées, par exemple les zombies tués avec l’œuf de zombie ; +- conserve les IDs et les sauvegardes Sanctuary v15/Red-Stoner v4 ; le snapshot client du panneau passant de trois à sept entrées, le protocole Red-Stoner passe de 7 à 8 et exige la mise à jour du client et du serveur. + ## `26.2.0-alpha.231` Modules modifiés : **Sanctuary `0.0.0-alpha.137`** et **Red-Stoner `0.0.0-alpha.18`**. diff --git a/README.md b/README.md index e7163e3..a1ba200 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans Version courante du pack distribué : **`26.2.0-alpha.231`**. Cette release distribue Sanctuary **`0.0.0-alpha.137`** et Red-Stoner **`0.0.0-alpha.18`** ; les huit autres modules conservent leur version issue de l'alpha.230. +Les prochains modules Sanctuary **`0.0.0-alpha.138`** et Red-Stoner **`0.0.0-alpha.19`** corrigent la cape blanche décorée avec deux faces planes utilisant l’UV exact du bouclier, empêchent la touche `E` de fermer le terminal pendant une recherche et étendent le panneau de trophées à huit lignes. Chaque statistique y possède une page de noms puis une page de valeurs ; un œuf d’apparition ajoute directement le classement des créatures correspondantes tuées. Les sauvegardes restent en données Sanctuary v15 et Red-Stoner v4 ; le snapshot du panneau passant à sept entrées, le protocole Red-Stoner passe à 8. + L’alpha.231 rend les sorties et le butin de la Backroom réellement trouvables sur ses cinq niveaux. Chaque niveau garantit désormais un coffre et un lit de sortie par cellule de 3 × 3 chunks dans les nouveaux chunks, sans ajouter de livres au butin. Le panneau de trophées devient une plaque verticale de chêne avec quatre lignes lisibles : titre et numéro de page, puis les trois places du podium. Un clic droit avance d’une page et `Maj` + clic droit revient à la précédente. JEI passe à la bêta Fabric `30.29.0.201`, qui corrige un crash lié à l’atlas de textures. Les sauvegardes et les chunks déjà générés restent compatibles ; Sanctuary conserve les données v15/protocole 24 et Red-Stoner les données v4/protocole 7. L’alpha.230 attribue les quêtes communautaires terminées à leur preneur et répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom. Elle ajoute aussi les classements publics et le panneau mural `redstoner:trophy_panel`, corrige la projection des couleurs et motifs de la cape blanche avec les sprites de bouclier, et permet d’équiper une poule adulte avec l’armure de loup vanilla en écailles de tatou. Les sauvegardes restent compatibles ; Sanctuary passe aux données v15 et Red-Stoner aux données v4 sans changer leurs protocoles réseau 24 et 7. diff --git a/build.gradle b/build.gradle index fa4419a..34913eb 100644 --- a/build.gradle +++ b/build.gradle @@ -1507,7 +1507,7 @@ tasks.register("verifyWorkspace") { || personalComputerMigration.compatibility?.redstoner_target_data_version != 3 || personalComputerMigration.compatibility?.redstoner_target_network_protocol != 5 || redstonerManifest.custom?.sanctuary26?.data_version != 4 - || redstonerManifest.custom?.sanctuary26?.network_protocol != 7 + || redstonerManifest.custom?.sanctuary26?.network_protocol != 8 || sanctuaryManifest.custom?.sanctuary26?.data_version < 8 || !sanctuaryIdentity.contains('SAVE_KEY = "server_identity"') || !personalComputerJava.contains('SAVE_KEY = "personal_computers"') @@ -2916,7 +2916,7 @@ tasks.register("verifyNotchEncounter") { def textureHash = MessageDigest.getInstance("SHA-256").digest(texture.bytes).encodeHex().toString() if (rootProject.itsalive_version != "0.0.0-alpha.38" || rootProject.ouch_version != "0.0.0-alpha.11" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || rootProject.masterkey_version != "0.0.0-alpha.9" || rootProject.pack_version != "26.2.0-alpha.231" || manifests.itsalive.custom?.sanctuary26?.data_version != 4 @@ -3010,7 +3010,7 @@ tasks.register("verifyMagicCarpet") { if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.iliketomoveit_version != "0.0.0-alpha.18" || rootProject.onlyfun_version != "0.0.0-alpha.27" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || manifests.iliketomoveit.custom?.sanctuary26?.data_version != 4 || manifests.iliketomoveit.custom?.sanctuary26?.network_protocol != 5 || manifests.iliketomoveit.depends?.sanctuary != ">=0.0.0-alpha.127" @@ -3641,7 +3641,7 @@ tasks.register("verifyProgressivePauseMenuRelease") { "sanctuary/src/main/resources/assets/sanctuary/lang/${locale}.json"))] } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || rootProject.onlyfun_version != "0.0.0-alpha.27" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.modules != [sanctuary: "0.0.0-alpha.108", onlyfun: "0.0.0-alpha.17"] @@ -3692,7 +3692,7 @@ tasks.register("verifyMirrorLavenderDrawerRelease") { def drawerRecipe = file("redstoner/src/main/resources/data/redstoner/recipe/drawer.json") if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.anotherworld_version != "0.0.0-alpha.57" - || rootProject.redstoner_version != "0.0.0-alpha.18" + || rootProject.redstoner_version != "0.0.0-alpha.19" || 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"] @@ -4210,6 +4210,7 @@ project(":redstoner") { group = "verification" description = "Checks the Sanctuary ranking API and Red-Stoner trophy-panel integration contract." inputs.file(rootProject.file("pack/migrations/trophy-rankings-data-v15-v4.json")) + inputs.file(rootProject.file("pack/migrations/alpha231-follow-up-cape-terminal-trophy.json")) inputs.files(fileTree("src/main/java") { include "**/*TrophyPanel*.java" }) inputs.files(fileTree(rootProject.file("sanctuary/src/main/java")) { include "**/api/ranking/*.java" }) inputs.files(fileTree("src/main/resources") { include "**/*trophy_panel*", "**/lang/*.json", "fabric.mod.json" }) @@ -4218,6 +4219,8 @@ project(":redstoner") { doLast { def migration = new JsonSlurper().parse(rootProject.file( "pack/migrations/trophy-rankings-data-v15-v4.json")) + def followUp = new JsonSlurper().parse(rootProject.file( + "pack/migrations/alpha231-follow-up-cape-terminal-trophy.json")) def redstonerManifest = new JsonSlurper().parse(file("src/main/resources/fabric.mod.json")) def sanctuaryManifest = new JsonSlurper().parse(rootProject.file( "sanctuary/src/main/resources/fabric.mod.json")) @@ -4238,7 +4241,7 @@ project(":redstoner") { def requiredKeys = [ "block.redstoner.trophy_panel", "message.redstoner.trophy_panel.not_owner", "message.redstoner.trophy_panel.configured", "block.redstoner.trophy_panel.page_title", - "block.redstoner.trophy_panel.podium"] + "block.redstoner.trophy_panel.player", "block.redstoner.trophy_panel.value"] def redstonerLocales = ["fr_fr", "en_us", "ru_ru"].collect { new JsonSlurper().parse(file("src/main/resources/assets/redstoner/lang/${it}.json")) } @@ -4246,10 +4249,10 @@ project(":redstoner") { new JsonSlurper().parse(rootProject.file( "sanctuary/src/main/resources/assets/sanctuary/lang/${it}.json")) } - if (rootProject.redstoner_version != "0.0.0-alpha.18" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + if (rootProject.redstoner_version != "0.0.0-alpha.19" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || redstonerManifest.custom?.sanctuary26?.data_version != 4 - || redstonerManifest.custom?.sanctuary26?.network_protocol != 7 + || redstonerManifest.custom?.sanctuary26?.network_protocol != 8 || sanctuaryManifest.custom?.sanctuary26?.data_version != 15 || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 24 || migration.modules?.sanctuary?.source_module_version != "0.0.0-alpha.134" @@ -4259,13 +4262,24 @@ project(":redstoner") { || migration.migration?.vanilla_statistics_imported_once != true || migration.compatibility?.existing_saves_supported != true || migration.compatibility?.network_protocols_changed != false + || followUp.status != "unreleased" + || followUp.modules?.sanctuary?.target_module_version != "0.0.0-alpha.138" + || followUp.modules?.redstoner?.target_module_version != "0.0.0-alpha.19" + || followUp.modules?.redstoner?.target_network_protocol != 8 + || followUp.fixes?.trophy_panel_visible_lines != 8 + || followUp.fixes?.trophy_panel_entries_per_view != 7 + || followUp.fixes?.trophy_panel_names_then_values != true + || followUp.fixes?.spawn_egg_maps_to_entity_killed_statistic != true + || followUp.compatibility?.persistent_data_formats_changed != false || !registry.contains('RedStonerMod.id("trophy_panel")') || !panel.contains("REFRESH_INTERVAL_TICKS = 20") || !panel.contains("CLIENT_SNAPSHOT_TAG") || !panel.contains("PAGE_CODEC.listOf(0, TrophyPanelRules.MAX_PAGES)") || !panel.contains("public boolean previousPage()") + || !panel.contains("CLIENT_VALUES_VIEW_TAG") || !block.contains("player.isSecondaryUseActive()") || !renderer.contains("TrophyPanelRenderState.LINE_COUNT") + || !renderer.contains("panel.valuesView()") || !renderer.contains("state.lightCoords, TEXT_COLOR, 0, 0") || model.textures?.wood != "minecraft:block/oak_planks" || model.textures?.containsKey("screen") @@ -4273,11 +4287,13 @@ project(":redstoner") { || model.elements[0].from != [0, 0, 14] || model.elements[0].to != [16, 16, 16] || !rankingApi.contains("public static RankingSnapshot snapshot") + || !rankingApi.contains("public static Component displayName") || !rankingApi.contains("!player.isCreative() && !player.isSpectator()") || !rankingData.contains("vanillaStatsImported") || !rankingData.contains("MAX_VALUE") || redstonerLocales.any { locale -> requiredKeys.any { !locale.containsKey(it) } } - || sanctuaryLocales.any { !it.containsKey("metric.sanctuary.ranking.play_time") } + || sanctuaryLocales.any { !it.containsKey("metric.sanctuary.ranking.play_time") + || !it.containsKey("metric.sanctuary.ranking.killed") } || !file("src/main/resources/assets/redstoner/models/block/trophy_panel.json").isFile() || !file("src/main/resources/data/redstoner/loot_table/blocks/trophy_panel.json").isFile() || !file("src/main/resources/data/redstoner/recipe/trophy_panel.json").isFile()) { @@ -4416,9 +4432,9 @@ 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.18" + if (project.version.toString() != "0.0.0-alpha.19" || manifest.custom?.sanctuary26?.data_version != 4 - || manifest.custom?.sanctuary26?.network_protocol != 7 + || manifest.custom?.sanctuary26?.network_protocol != 8 || terminalMigration.source?.pack_version != "26.2.0-alpha.217" || terminalMigration.source?.modules != [ redstoner: "0.0.0-alpha.10", onlyfun: "0.0.0-alpha.24", sanctuary: "0.0.0-alpha.120"] @@ -4592,7 +4608,7 @@ tasks.register("verifyShopPricingLayoutRelease") { def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.onlyfun_version != "0.0.0-alpha.27" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.192" || migration.target?.pack_version != "26.2.0-alpha.193" @@ -4686,7 +4702,7 @@ tasks.register("verifyShopCsvBossGatesRelease") { def offerCatalog = file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.onlyfun_version != "0.0.0-alpha.27" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.194" || migration.source?.modules != [onlyfun: "0.0.0-alpha.18", sanctuary: "0.0.0-alpha.110"] @@ -4779,7 +4795,7 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") { def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.itsalive_version != "0.0.0-alpha.38" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.196" || migration.target?.pack_version != "26.2.0-alpha.197" || migration.target?.modules != [itsalive: "0.0.0-alpha.29", sanctuary: "0.0.0-alpha.113"] @@ -4879,7 +4895,7 @@ tasks.register("verifyQuestRequestContractsRelease") { def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.itsalive_version != "0.0.0-alpha.38" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.198" || migration.source?.modules?.sanctuary != "0.0.0-alpha.114" || migration.target?.pack_version != "26.2.0-alpha.199" @@ -4999,7 +5015,7 @@ tasks.register("verifyQuestRequestEscrowRelease") { 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.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.200" || migration.source?.modules?.sanctuary != "0.0.0-alpha.115" || migration.target?.pack_version != "26.2.0-alpha.201" @@ -5056,7 +5072,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { || rootProject.anotherworld_version != "0.0.0-alpha.57" || rootProject.itsalive_version != "0.0.0-alpha.38" || rootProject.iliketomoveit_version != "0.0.0-alpha.18" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.201" || migration.source?.modules != [ itsalive: "0.0.0-alpha.30", @@ -5214,7 +5230,7 @@ tasks.register("verifySafeVaultFeature") { }.keySet() if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.anotherworld_version != "0.0.0-alpha.57" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.schema_version != 1 || migration.source?.pack_version != "26.2.0-alpha.201" || migration.target?.pack_version != "26.2.0-alpha.202" @@ -6074,7 +6090,7 @@ tasks.register("verifyAlpha210Release") { it.path == "mods/jei-26.2-fabric-30.29.0.201.jar" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || rootProject.onlyfun_version != "0.0.0-alpha.27" || rootProject.iliketomoveit_version != "0.0.0-alpha.18" || rootProject.itsalive_version != "0.0.0-alpha.38" @@ -6176,7 +6192,7 @@ tasks.register("verifyAlpha211Release") { itsalive: "0.0.0-alpha.38", ouch: "0.0.0-alpha.11", masterkey: "0.0.0-alpha.9"] - def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.137"] + def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.138"] def expectedDataVersions = [ambiance: 2, itsalive: 3, onlyfun: 2, ouch: 1, sanctuary: 11] def workspaceDataVersions = [ambiance: 2, itsalive: 4, onlyfun: 2, ouch: 1, sanctuary: 15] def expectedProtocols = [ambiance: 6, itsalive: 4, onlyfun: 3, ouch: 1, sanctuary: 22] @@ -6276,7 +6292,7 @@ tasks.register("verifyAlpha212Release") { it.path == "mods/jei-26.2-fabric-30.29.0.201.jar" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.211" || migration.source?.modules != [sanctuary: "0.0.0-alpha.119"] || migration.target?.pack_version != "26.2.0-alpha.212" @@ -6767,7 +6783,7 @@ tasks.register("verifyAlpha218Release") { "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.redstoner_version != "0.0.0-alpha.18" + || rootProject.redstoner_version != "0.0.0-alpha.19" || currentModules.findAll { id, version -> id != "redstoner" && id != "sanctuary" && id != "onlyfun" && id != "iliketomoveit" }.any { @@ -6775,7 +6791,7 @@ tasks.register("verifyAlpha218Release") { } || rootProject.onlyfun_version != "0.0.0-alpha.27" || rootProject.iliketomoveit_version != "0.0.0-alpha.18" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.217" || migration.source?.modules != sourceModules || migration.target?.pack_version != "26.2.0-alpha.218" @@ -6796,14 +6812,14 @@ tasks.register("verifyAlpha218Release") { || migration.distribution?.packwiz_index_refreshed != true || migration.distribution?.prism_modrinth_index_updated != true || manifests.redstoner.custom?.sanctuary26?.data_version != 4 - || manifests.redstoner.custom?.sanctuary26?.network_protocol != 7 + || manifests.redstoner.custom?.sanctuary26?.network_protocol != 8 || manifests.onlyfun.custom?.sanctuary26?.data_version != 2 || manifests.onlyfun.custom?.sanctuary26?.network_protocol != 3 || manifests.sanctuary.custom?.sanctuary26?.data_version != 15 || manifests.sanctuary.custom?.sanctuary26?.network_protocol != 24 || !rules.contains("MAX_CONTAINERS = 128") || !rules.contains("PAGE_SIZE = 54") - || !networking.contains("PROTOCOL_VERSION = 7") + || !networking.contains("PROTOCOL_VERSION = 8") || !onlyFunPolicy.contains('namespace.equals("redstoner") && path.equals("storage_terminal")') || !blackMarketPolicy.contains('namespace.equals("redstoner") && path.equals("storage_terminal")') || release.pack_version != rootProject.pack_version @@ -6861,7 +6877,7 @@ tasks.register("verifyAlpha219Release") { ambiance: "0.0.0-alpha.22", sanctuary: "0.0.0-alpha.137", iliketomoveit: "0.0.0-alpha.18", onlyfun: "0.0.0-alpha.27", itsalive: "0.0.0-alpha.38", ouch: "0.0.0-alpha.11", masterkey: "0.0.0-alpha.9"] - def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.137"] + def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.138"] def requiredJars = currentModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } @@ -6956,7 +6972,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.231" - || rootProject.redstoner_version != "0.0.0-alpha.18" + || rootProject.redstoner_version != "0.0.0-alpha.19" || 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" @@ -6981,7 +6997,7 @@ tasks.register("verifyAlpha220Release") { || migration.compatibility?.redstoner_source_network_protocol != 6 || migration.compatibility?.redstoner_target_network_protocol != 7 || manifest.custom?.sanctuary26?.data_version != 4 - || manifest.custom?.sanctuary26?.network_protocol != 7 + || manifest.custom?.sanctuary26?.network_protocol != 8 || !menu.contains("extends AbstractContainerMenu") || !menu.contains("addStandardInventorySlots") || !menu.contains("SanctuaryPlayerInventoryView.unlockedRows") @@ -6992,7 +7008,7 @@ tasks.register("verifyAlpha220Release") { || !screen.contains("extends AbstractContainerScreen") || !screen.contains("textures/gui/container/generic_54.png") || screen.contains('Button.builder(Component.translatable("gui.done")') - || !networking.contains("PROTOCOL_VERSION = 7") + || !networking.contains("PROTOCOL_VERSION = 8") || networking.contains("storage_terminal_snapshot") || networking.contains("storage_terminal_action") || release.pack_version != rootProject.pack_version @@ -7185,8 +7201,8 @@ tasks.register("verifyAlpha222Release") { it.path == "mods/jei-26.2-fabric-30.29.0.201.jar" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.redstoner_version != "0.0.0-alpha.18" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.redstoner_version != "0.0.0-alpha.19" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.221" || migration.source?.modules != [ redstoner: "0.0.0-alpha.12", sanctuary: "0.0.0-alpha.122"] @@ -7227,7 +7243,7 @@ tasks.register("verifyAlpha222Release") { || migration.distribution?.jei?.modrinth_version_id != "vvwsu2BR" || migration.distribution?.jei?.packwiz_and_prism_synchronized != true || manifests.redstoner.custom?.sanctuary26?.data_version != 4 - || manifests.redstoner.custom?.sanctuary26?.network_protocol != 7 + || manifests.redstoner.custom?.sanctuary26?.network_protocol != 8 || manifests.sanctuary.custom?.sanctuary26?.data_version != 15 || manifests.sanctuary.custom?.sanctuary26?.network_protocol != 24 || !rules.contains("MAX_SEARCH_LENGTH = 32") @@ -7243,7 +7259,7 @@ tasks.register("verifyAlpha222Release") { || !screen.contains("searchBox.setResponder(this::searchChanged)") || !smoke.contains("verifySearch()") || !smoke.contains("verifyStableLayout()") - || !networking.contains("PROTOCOL_VERSION = 7") + || !networking.contains("PROTOCOL_VERSION = 8") || networking.contains("storage_terminal_search") || !questPayload.contains("MAX_ENTRIES = 8") || !questPayload.contains('SanctuaryMod.id("s2c/quest_tracker")') @@ -7280,16 +7296,22 @@ tasks.register("verifyAlpha222Release") { tasks.register("verifyStorageTerminalSearchFocus") { group = "verification" - description = "Checks clickable storage-terminal search focus and vanilla inventory-key closing." + description = "Checks clickable storage-terminal search focus and scoped inventory-key capture." 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/storage/StorageTerminalKeyRules.java")) doLast { def screen = file( "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/gui/StorageTerminalScreen.java").text + def keyRules = file( + "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalKeyRules.java").text if (screen.contains("setInitialFocus(searchBox)") || !screen.contains("searchBox.isMouseOver(event.x(), event.y())") || !screen.contains("minecraft.options.keyInventory.matches(event)") - || screen.count("clearSearchFocus();") != 2 + || screen.count("clearSearchFocus();") != 1 + || !screen.contains("StorageTerminalKeyRules.capturesInventoryKey") + || !keyRules.contains("return searchFocused && inventoryKey") || !screen.contains("searchBox.setFocused(false)") || !screen.contains("setFocused(null)") || !screen.contains("return super.mouseClicked(event, doubleClick)") @@ -7398,7 +7420,7 @@ tasks.register("verifyAlpha223Release") { it.path == "mods/jei-26.2-fabric-30.29.0.201.jar" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.222" || migration.source?.modules != [sanctuary: "0.0.0-alpha.124"] || migration.target?.pack_version != "26.2.0-alpha.223" @@ -7519,7 +7541,7 @@ tasks.register("verifyAlpha224Release") { def currentModules = targetModules + [ itsalive: "0.0.0-alpha.38", iliketomoveit: "0.0.0-alpha.18", onlyfun: "0.0.0-alpha.27", sanctuary: "0.0.0-alpha.137"] - def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.137"] + def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.138"] def inventorySort = file( "sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryInventorySort.java").text def clientProgress = file( @@ -7641,11 +7663,12 @@ tasks.register("verifyAlpha225Release") { sanctuary: "0.0.0-alpha.129"] def currentModules = targetModules + [sanctuary: "0.0.0-alpha.137"] def currentWorkspaceModules = currentModules + [ - anotherworld: "0.0.0-alpha.57", sanctuary: "0.0.0-alpha.137"] + anotherworld: "0.0.0-alpha.57", sanctuary: "0.0.0-alpha.138"] def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def releasedModules = workspaceModules + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } @@ -7805,12 +7828,13 @@ tasks.register("verifyAlpha226Release") { def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def releasedModules = workspaceModules + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.225" || migration.source?.modules != [sanctuary: "0.0.0-alpha.129"] @@ -7896,13 +7920,14 @@ tasks.register("verifyAlpha227Release") { def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def releasedModules = workspaceModules + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.231" || rootProject.anotherworld_version != "0.0.0-alpha.57" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.226" || migration.source?.modules != [ @@ -7994,13 +8019,14 @@ tasks.register("verifyAlpha228Release") { def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def releasedModules = workspaceModules + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.redstoner_version != "0.0.0-alpha.18" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.redstoner_version != "0.0.0-alpha.19" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.227" || migration.source?.modules != [ @@ -8033,7 +8059,7 @@ tasks.register("verifyAlpha228Release") { || migration.distribution?.packwiz_index_refreshed != true || migration.distribution?.prism_modrinth_index_updated != true || redStonerManifest.custom?.sanctuary26?.data_version != 4 - || redStonerManifest.custom?.sanctuary26?.network_protocol != 7 + || redStonerManifest.custom?.sanctuary26?.network_protocol != 8 || sanctuaryManifest.custom?.sanctuary26?.data_version != 15 || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 24 || !terminalSearch.contains("DataComponents.CONTAINER") @@ -8099,7 +8125,8 @@ tasks.register("verifyAlpha229Release") { def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def releasedModules = workspaceModules + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } @@ -8109,8 +8136,8 @@ tasks.register("verifyAlpha229Release") { "screen.redstoner.storage_terminal.sort.count_ascending", "screen.redstoner.storage_terminal.sort.count_descending"] if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.redstoner_version != "0.0.0-alpha.18" - || rootProject.sanctuary_version != "0.0.0-alpha.137" + || rootProject.redstoner_version != "0.0.0-alpha.19" + || rootProject.sanctuary_version != "0.0.0-alpha.138" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.228" || migration.source?.modules != [redstoner: "0.0.0-alpha.15"] @@ -8135,7 +8162,7 @@ tasks.register("verifyAlpha229Release") { || migration.distribution?.packwiz_index_refreshed != true || migration.distribution?.prism_modrinth_index_updated != true || manifest.custom?.sanctuary26?.data_version != 4 - || manifest.custom?.sanctuary26?.network_protocol != 7 + || manifest.custom?.sanctuary26?.network_protocol != 8 || locales.any { locale -> requiredTranslationKeys.any { !locale.containsKey(it) } } || !sort.contains('ALPHABETICAL("alphabetical")') || !sort.contains('COUNT_ASCENDING("count_ascending")') @@ -8187,15 +8214,16 @@ tasks.register("verifyAlpha230Release") { def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def releasedModules = workspaceModules + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } def indexHash = java.security.MessageDigest.getInstance("SHA-256") .digest(packwizIndexFile.bytes).encodeHex().toString() if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" - || rootProject.redstoner_version != "0.0.0-alpha.18" + || rootProject.sanctuary_version != "0.0.0-alpha.138" + || rootProject.redstoner_version != "0.0.0-alpha.19" || migration.schema_version != 1 || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.229" @@ -8227,7 +8255,7 @@ tasks.register("verifyAlpha230Release") { || manifests.sanctuary.custom?.sanctuary26?.data_version != 15 || manifests.sanctuary.custom?.sanctuary26?.network_protocol != 24 || manifests.redstoner.custom?.sanctuary26?.data_version != 4 - || manifests.redstoner.custom?.sanctuary26?.network_protocol != 7 + || manifests.redstoner.custom?.sanctuary26?.network_protocol != 8 || release.pack_version != "26.2.0-alpha.231" || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.231.mrpack" || prism.versionId != "26.2.0-alpha.231" @@ -8293,14 +8321,16 @@ tasks.register("verifyAlpha231Release") { def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } - def requiredJars = workspaceModules.collect { id, version -> + def releasedModules = workspaceModules + [ + sanctuary: "0.0.0-alpha.137", redstoner: "0.0.0-alpha.18"] + def requiredJars = releasedModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } def indexHash = java.security.MessageDigest.getInstance("SHA-256") .digest(packwizIndexFile.bytes).encodeHex().toString() if (rootProject.pack_version != "26.2.0-alpha.231" - || rootProject.sanctuary_version != "0.0.0-alpha.137" - || rootProject.redstoner_version != "0.0.0-alpha.18" + || rootProject.sanctuary_version != "0.0.0-alpha.138" + || rootProject.redstoner_version != "0.0.0-alpha.19" || migration.schema_version != 1 || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.230" @@ -8340,7 +8370,7 @@ tasks.register("verifyAlpha231Release") { || manifests.sanctuary.custom?.sanctuary26?.data_version != 15 || manifests.sanctuary.custom?.sanctuary26?.network_protocol != 24 || manifests.redstoner.custom?.sanctuary26?.data_version != 4 - || manifests.redstoner.custom?.sanctuary26?.network_protocol != 7 + || manifests.redstoner.custom?.sanctuary26?.network_protocol != 8 || !layout.contains("LOOT_CHEST_CELL_SIZE_CHUNKS = 3") || !layout.contains("EXIT_BED_CELL_SIZE_CHUNKS = 3") || !layout.contains("lootChestChunk(") @@ -8355,7 +8385,7 @@ tasks.register("verifyAlpha231Release") { || !trophyRenderer.contains("MAX_TEXT_WIDTH = 90") || !trophyRenderer.contains("block.redstoner.trophy_panel.page_title") || !trophyRenderer.contains("Font.DisplayMode.POLYGON_OFFSET") - || !trophyState.contains("LINE_COUNT = 4") + || !trophyState.contains("LINE_COUNT = TrophyPanelRules.LINE_COUNT") || trophyModel.parent != "minecraft:block/block" || jeiFiles.size() != 1 || jeiFiles[0].hashes?.sha1 != "e82447aff07e87a372cf817bc9c28b0d77b6f55c" diff --git a/gradle.properties b/gradle.properties index 01f1bc7..11c50e0 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.18 +redstoner_version=0.0.0-alpha.19 redstoner_lifecycle=active anotherworld_version=0.0.0-alpha.57 anotherworld_lifecycle=active @@ -28,7 +28,7 @@ iliketomoveit_version=0.0.0-alpha.18 iliketomoveit_lifecycle=active onlyfun_version=0.0.0-alpha.27 onlyfun_lifecycle=active -sanctuary_version=0.0.0-alpha.137 +sanctuary_version=0.0.0-alpha.138 sanctuary_lifecycle=active pack_version=26.2.0-alpha.231 maven_group=fr.koka99cab.sanctuary26 diff --git a/pack/migrations/alpha231-follow-up-cape-terminal-trophy.json b/pack/migrations/alpha231-follow-up-cape-terminal-trophy.json new file mode 100644 index 0000000..0671324 --- /dev/null +++ b/pack/migrations/alpha231-follow-up-cape-terminal-trophy.json @@ -0,0 +1,36 @@ +{ + "schema_version": 1, + "status": "unreleased", + "modules": { + "sanctuary": { + "source_module_version": "0.0.0-alpha.137", + "target_module_version": "0.0.0-alpha.138", + "data_version": 15, + "network_protocol": 24 + }, + "redstoner": { + "source_module_version": "0.0.0-alpha.18", + "target_module_version": "0.0.0-alpha.19", + "data_version": 4, + "source_network_protocol": 7, + "target_network_protocol": 8 + } + }, + "fixes": { + "banner_cape_flat_shield_uv_faces": true, + "banner_cape_double_sided_culling": true, + "terminal_inventory_key_captured_while_searching": true, + "trophy_panel_visible_lines": 8, + "trophy_panel_entries_per_view": 7, + "trophy_panel_names_then_values": true, + "spawn_egg_maps_to_entity_killed_statistic": true + }, + "compatibility": { + "existing_saves_supported": true, + "new_world_required": false, + "save_reset_required": false, + "persistent_data_formats_changed": false, + "network_protocols_changed": true, + "client_server_update_required": true + } +} diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/block/entity/TrophyPanelBlockEntity.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/block/entity/TrophyPanelBlockEntity.java index 2d3c838..963ba6e 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/block/entity/TrophyPanelBlockEntity.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/block/entity/TrophyPanelBlockEntity.java @@ -36,6 +36,7 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn private static final int DATA_VERSION = 1; private static final int REFRESH_INTERVAL_TICKS = 20; private static final String CLIENT_SNAPSHOT_TAG = "VisibleSnapshot"; + private static final String CLIENT_VALUES_VIEW_TAG = "ValuesView"; private static final Codec PAGE_CODEC = RecordCodecBuilder.create(instance -> instance.group( Identifier.CODEC.fieldOf("metric").forGetter(Page::metricId), ItemStack.OPTIONAL_CODEC.fieldOf("icon").forGetter(Page::icon) @@ -60,6 +61,7 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn private String ownerName = "?"; private List pages = List.of(defaultPage()); private int currentPage; + private boolean valuesView; private long lastRefreshTick = Long.MIN_VALUE; private ClientSnapshot visibleSnapshot = ClientSnapshot.empty(SanctuaryRankingApi.defaultMetric()); @@ -104,6 +106,7 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn currentPage = Math.max(0, pages.indexOf(pages.stream() .filter(page -> offered.stream().anyMatch(metric -> metric.id().equals(page.metricId()))) .findFirst().orElse(pages.getFirst()))); + valuesView = false; ownerName = cleanName(player.getGameProfile().name()); syncPersistent(); if (level instanceof ServerLevel serverLevel) refresh(serverLevel); @@ -120,7 +123,10 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn private boolean movePage(int offset) { if (!writable() || pages.isEmpty()) return false; - currentPage = TrophyPanelRules.pageByOffset(currentPage, pages.size(), offset); + TrophyPanelRules.DisplayPage displayPage = TrophyPanelRules.displayPageByOffset( + currentPage, valuesView, pages.size(), offset); + currentPage = displayPage.metricPage(); + valuesView = displayPage.valuesView(); syncPersistent(); if (level instanceof ServerLevel serverLevel) refresh(serverLevel); return true; @@ -131,11 +137,15 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn } public int pageNumber() { - return currentPage + 1; + return TrophyPanelRules.displayPageNumber(currentPage, valuesView, pages.size()); } public int pageCount() { - return pages.size(); + return TrophyPanelRules.displayPageCount(pages.size()); + } + + public boolean valuesView() { + return valuesView; } public String ownerName() { @@ -174,6 +184,7 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn pages = input.read("Pages", PAGE_CODEC.listOf(0, TrophyPanelRules.MAX_PAGES)) .map(TrophyPanelBlockEntity::sanitizePages).orElseGet(() -> List.of(defaultPage())); currentPage = Math.clamp(input.getIntOr("CurrentPage", 0), 0, pages.size() - 1); + valuesView = input.getBooleanOr(CLIENT_VALUES_VIEW_TAG, false); visibleSnapshot = input.read(CLIENT_SNAPSHOT_TAG, CLIENT_SNAPSHOT_CODEC) .orElseGet(() -> ClientSnapshot.empty(metricFor(currentPage().metricId()))); } @@ -187,6 +198,7 @@ public final class TrophyPanelBlockEntity extends BlockEntity implements ItemOwn public CompoundTag getUpdateTag(HolderLookup.Provider lookup) { CompoundTag tag = saveCustomOnly(lookup); tag.store(CLIENT_SNAPSHOT_TAG, CLIENT_SNAPSHOT_CODEC, visibleSnapshot); + tag.putBoolean(CLIENT_VALUES_VIEW_TAG, valuesView); return tag; } 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 fa5bc26..37a2020 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,5 +1,6 @@ package fr.koka99cab.sanctuary26.redstoner.client.gui; +import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalKeyRules; import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalMenu; import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalRules; import fr.koka99cab.sanctuary26.redstoner.storage.StorageTerminalSort; @@ -139,10 +140,9 @@ public final class StorageTerminalScreen extends AbstractContainerScreen { private static final int MAX_TEXT_WIDTH = 90; private static final float TEXT_SCALE = 0.010416667F; - private static final float[] LINE_HEIGHTS = {0.70F, 0.59F, 0.48F, 0.37F}; + private static final float[] LINE_HEIGHTS = {0.86F, 0.75F, 0.64F, 0.53F, 0.42F, 0.31F, 0.20F, 0.09F}; private static final int TEXT_COLOR = 0xFF3D281A; private final Font font; @@ -39,26 +43,27 @@ public final class TrophyPanelBlockEntityRenderer float tickProgress, Vec3 cameraPos, ModelFeatureRenderer.CrumblingOverlay crumblingOverlay) { BlockEntityRenderer.super.extractRenderState(panel, state, tickProgress, cameraPos, crumblingOverlay); state.facing = panel.getBlockState().getValue(TrophyPanelBlock.FACING); + Arrays.fill(state.lines, Component.empty()); ItemStack icon = panel.currentPage().icon(); TrophyPanelBlockEntity.ClientSnapshot snapshot = panel.visibleSnapshot(); RankingUnit unit = snapshot.unit(); - Component metric = !icon.isEmpty() ? Component.translatable(snapshot.translationKey(), icon.getHoverName()) - : Component.translatable(snapshot.translationKey()); + Component metric = SanctuaryRankingApi.displayName( + new RankingMetric(snapshot.metricId(), snapshot.translationKey(), unit), icon); state.lines[0] = Component.translatable("block.redstoner.trophy_panel.page_title", panel.pageNumber(), panel.pageCount(), metric); if (snapshot.podium().isEmpty()) { state.lines[1] = Component.translatable("block.redstoner.trophy_panel.no_score"); - state.lines[2] = Component.empty(); - state.lines[3] = Component.empty(); } else { - for (int index = 0; index < 3; index++) { + for (int index = 0; index < TrophyPanelRules.ENTRIES_PER_VIEW; index++) { if (index >= snapshot.podium().size()) { - state.lines[index + 1] = Component.empty(); - continue; + break; } TrophyPanelBlockEntity.ClientEntry entry = snapshot.podium().get(index); - state.lines[index + 1] = Component.translatable("block.redstoner.trophy_panel.podium", - entry.rank(), entry.name(), unit.format(entry.value())); + state.lines[index + 1] = panel.valuesView() + ? Component.translatable("block.redstoner.trophy_panel.value", + entry.rank(), unit.format(entry.value())) + : Component.translatable("block.redstoner.trophy_panel.player", + entry.rank(), entry.name()); } } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/renderer/state/TrophyPanelRenderState.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/renderer/state/TrophyPanelRenderState.java index bebf7e4..b86645c 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/renderer/state/TrophyPanelRenderState.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/client/renderer/state/TrophyPanelRenderState.java @@ -1,13 +1,16 @@ package fr.koka99cab.sanctuary26.redstoner.client.renderer.state; +import fr.koka99cab.sanctuary26.redstoner.trophy.TrophyPanelRules; import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; import net.minecraft.core.Direction; import net.minecraft.network.chat.Component; public final class TrophyPanelRenderState extends BlockEntityRenderState { - public static final int LINE_COUNT = 4; - public final Component[] lines = { - Component.empty(), Component.empty(), Component.empty(), Component.empty() - }; + public static final int LINE_COUNT = TrophyPanelRules.LINE_COUNT; + public final Component[] lines = new Component[LINE_COUNT]; public Direction facing = Direction.NORTH; + + public TrophyPanelRenderState() { + java.util.Arrays.fill(lines, Component.empty()); + } } diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java index 80d29e6..a2d4de9 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/network/RedStonerNetworking.java @@ -12,7 +12,7 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.phys.Vec3; public final class RedStonerNetworking { - public static final int PROTOCOL_VERSION = 7; + public static final int PROTOCOL_VERSION = 8; private static boolean initialized; private RedStonerNetworking() { diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalKeyRules.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalKeyRules.java new file mode 100644 index 0000000..dc63539 --- /dev/null +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalKeyRules.java @@ -0,0 +1,10 @@ +package fr.koka99cab.sanctuary26.redstoner.storage; + +/** Keyboard routing shared by the terminal screen and its deterministic smoke test. */ +public final class StorageTerminalKeyRules { + private StorageTerminalKeyRules() {} + + public static boolean capturesInventoryKey(boolean searchFocused, boolean inventoryKey) { + return searchFocused && inventoryKey; + } +} diff --git a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRules.java b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRules.java index 2c2da3d..81c95dd 100644 --- a/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRules.java +++ b/redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRules.java @@ -9,6 +9,9 @@ import net.minecraft.resources.Identifier; /** Pure bounded rules shared by the trophy panel and its smoke tests. */ public final class TrophyPanelRules { public static final int MAX_PAGES = 16; + public static final int LINE_COUNT = 8; + public static final int ENTRIES_PER_VIEW = LINE_COUNT - 1; + private static final int VIEWS_PER_METRIC = 2; private TrophyPanelRules() {} @@ -28,10 +31,33 @@ public final class TrophyPanelRules { return pageCount <= 0 ? 0 : Math.floorMod(currentPage + offset, pageCount); } + public static DisplayPage displayPageByOffset(int metricPage, boolean valuesView, + int metricCount, int offset) { + int safeMetricCount = Math.max(1, metricCount); + int safeMetricPage = Math.clamp(metricPage, 0, safeMetricCount - 1); + int currentDisplayPage = safeMetricPage * VIEWS_PER_METRIC + (valuesView ? 1 : 0); + int displayPage = Math.floorMod(currentDisplayPage + offset, + safeMetricCount * VIEWS_PER_METRIC); + return new DisplayPage(displayPage / VIEWS_PER_METRIC, + displayPage % VIEWS_PER_METRIC == 1); + } + + public static int displayPageNumber(int metricPage, boolean valuesView, int metricCount) { + int safeMetricCount = Math.max(1, metricCount); + return Math.clamp(metricPage, 0, safeMetricCount - 1) * VIEWS_PER_METRIC + + (valuesView ? 2 : 1); + } + + public static int displayPageCount(int metricCount) { + return Math.max(1, metricCount) * VIEWS_PER_METRIC; + } + public static List mergeMetricIds(List current, List offered) { LinkedHashSet merged = new LinkedHashSet<>(); if (current != null) merged.addAll(current); if (offered != null) merged.addAll(offered); return List.copyOf(new ArrayList<>(merged).subList(0, Math.min(MAX_PAGES, merged.size()))); } + + public record DisplayPage(int metricPage, boolean valuesView) {} } 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 9d795bd..74a04e1 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/en_us.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/en_us.json @@ -66,6 +66,8 @@ "block.redstoner.trophy_panel": "Trophy Panel", "block.redstoner.trophy_panel.page_title": "[%s/%s] %s", "block.redstoner.trophy_panel.podium": "#%s %s · %s", + "block.redstoner.trophy_panel.player": "#%s %s", + "block.redstoner.trophy_panel.value": "#%s %s", "block.redstoner.trophy_panel.no_score": "No score yet", "block.redstoner.trophy_panel.champion": "#1 %s · %s", "block.redstoner.trophy_panel.owner_rank": "%s · #%s · %s", 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 d9bbc3a..b1e7d16 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/fr_fr.json @@ -66,6 +66,8 @@ "block.redstoner.trophy_panel": "Panneau de trophées", "block.redstoner.trophy_panel.page_title": "[%s/%s] %s", "block.redstoner.trophy_panel.podium": "#%s %s · %s", + "block.redstoner.trophy_panel.player": "#%s %s", + "block.redstoner.trophy_panel.value": "#%s %s", "block.redstoner.trophy_panel.no_score": "Aucun score", "block.redstoner.trophy_panel.champion": "#1 %s · %s", "block.redstoner.trophy_panel.owner_rank": "%s · #%s · %s", 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 d217ec2..3f20be1 100644 --- a/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json +++ b/redstoner/src/main/resources/assets/redstoner/lang/ru_ru.json @@ -66,6 +66,8 @@ "block.redstoner.trophy_panel": "Панель трофеев", "block.redstoner.trophy_panel.page_title": "[%s/%s] %s", "block.redstoner.trophy_panel.podium": "#%s %s · %s", + "block.redstoner.trophy_panel.player": "#%s %s", + "block.redstoner.trophy_panel.value": "#%s %s", "block.redstoner.trophy_panel.no_score": "Результатов пока нет", "block.redstoner.trophy_panel.champion": "#1 %s · %s", "block.redstoner.trophy_panel.owner_rank": "%s · #%s · %s", diff --git a/redstoner/src/main/resources/fabric.mod.json b/redstoner/src/main/resources/fabric.mod.json index 4bcf56b..44cee24 100644 --- a/redstoner/src/main/resources/fabric.mod.json +++ b/redstoner/src/main/resources/fabric.mod.json @@ -31,7 +31,7 @@ "system_type": "gameplay-system", "namespace": "redstoner", "data_version": 4, - "network_protocol": 7, + "network_protocol": 8, "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", "descriptions": { 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 f48d6d5..bfe455a 100644 --- a/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java +++ b/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/storage/StorageTerminalSmoke.java @@ -27,6 +27,7 @@ public final class StorageTerminalSmoke { verifyPagination(); verifyAggregationAndTransfers(); verifySearch(); + verifySearchKeyboardRouting(); verifySorting(); verifyStableLayout(); } @@ -145,6 +146,15 @@ public final class StorageTerminalSmoke { "Terminal search retained an unrelated item"); } + private static void verifySearchKeyboardRouting() { + require(StorageTerminalKeyRules.capturesInventoryKey(true, true), + "The inventory key escaped the focused terminal search field"); + require(!StorageTerminalKeyRules.capturesInventoryKey(false, true), + "The inventory key no longer closes an unfocused terminal"); + require(!StorageTerminalKeyRules.capturesInventoryKey(true, false), + "The terminal captured an unrelated search-field key"); + } + private static Holder stackable(Item item) { return new Holder.Direct<>(item, DataComponentMap.builder() .set(DataComponents.MAX_STACK_SIZE, 64).build()); diff --git a/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRulesSmoke.java b/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRulesSmoke.java index 367ac74..2522af4 100644 --- a/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRulesSmoke.java +++ b/redstoner/src/test/java/fr/koka99cab/sanctuary26/redstoner/trophy/TrophyPanelRulesSmoke.java @@ -19,6 +19,18 @@ public final class TrophyPanelRulesSmoke { "Reverse public page cycling does not wrap"); require(TrophyPanelRules.pageByOffset(4, 0, 1) == 0, "An empty panel produced an invalid page index"); + TrophyPanelRules.DisplayPage values = TrophyPanelRules.displayPageByOffset(0, false, 2, 1); + TrophyPanelRules.DisplayPage secondMetric = TrophyPanelRules.displayPageByOffset( + values.metricPage(), values.valuesView(), 2, 1); + TrophyPanelRules.DisplayPage wrapped = TrophyPanelRules.displayPageByOffset(0, false, 2, -1); + require(values.metricPage() == 0 && values.valuesView() + && secondMetric.metricPage() == 1 && !secondMetric.valuesView() + && wrapped.metricPage() == 1 && wrapped.valuesView(), + "Name and value views do not cycle as consecutive public pages"); + require(TrophyPanelRules.LINE_COUNT == 8 && TrophyPanelRules.ENTRIES_PER_VIEW == 7 + && TrophyPanelRules.displayPageNumber(1, true, 2) == 4 + && TrophyPanelRules.displayPageCount(2) == 4, + "The panel does not use eight lines or number both metric views correctly"); Identifier first = Identifier.parse("sanctuary:first"); Identifier second = Identifier.parse("sanctuary:second"); diff --git a/sanctuary/build.gradle b/sanctuary/build.gradle index 3f08846..d75e20a 100644 --- a/sanctuary/build.gradle +++ b/sanctuary/build.gradle @@ -124,6 +124,14 @@ tasks.register("capeSyncPayloadSmoke", JavaExec) { mainClass = "fr.koka99cab.sanctuary26.sanctuary.network.CapeSyncPayloadSmoke" } +tasks.register("capeModelSmoke", JavaExec) { + group = "verification" + description = "Checks the double-sided shield-pattern plane used by banner capes." + dependsOn tasks.named("testClasses") + classpath = sourceSets.test.runtimeClasspath + mainClass = "fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryBannerCapeModelSmoke" +} + tasks.register("chickenArmorSmoke", JavaExec) { group = "verification" description = "Checks that only adult unarmored chickens accept armadillo-scute armor." @@ -207,7 +215,7 @@ tasks.register("verifyBlackMarket") { def questData = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardData.java").text def quests = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardService.java").text def inventoryView = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/inventory/SanctuaryPlayerInventoryView.java").text - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || !data.contains('SAVE_KEY = "black_market"') || !data.contains("DATA_VERSION = 2") || !data.contains("ItemStack.CODEC.fieldOf(\"item\")") || !data.contains("replaceListing") || !service.contains("LISTING_FEE_RUBIES = 1") || !service.contains("COMMISSION_PERCENT = 5") @@ -2923,7 +2931,7 @@ tasks.register("verifyShopCurrencyRowsRelease") { "src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java").text def shop = file( "src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java").text - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.modules?.sanctuary != "0.0.0-alpha.117" || migration.shop_currency_inventory?.all_unlocked_rows_counted != true || migration.shop_currency_inventory?.overflow_rows_counted != true @@ -3089,7 +3097,7 @@ tasks.register("verifyProgressivePauseMenu") { "pack/migrations/26.2.0-alpha.190-progressive-pause-menu-to-alpha.191.json")) def pauseMenu = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/PauseScreenMixin.java").text def costs = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/progression/SanctuaryProgressionCosts.java").text - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.pack_version != "26.2.0-alpha.191" || migration.target?.modules?.sanctuary != "0.0.0-alpha.109" || migration.pause_menu?.progression_full_width != true @@ -3127,7 +3135,7 @@ tasks.register("verifyShopPricingLayout") { "pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json")) def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryShopScreen.java").text def progression = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopProgression.java").text - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.modules?.sanctuary != "0.0.0-alpha.110" || migration.shop_layout?.locked_offer_buttons != 6 || migration.shop_layout?.button_slots != [4, 5, 6, 7, 8, 9] @@ -3158,7 +3166,7 @@ tasks.register("verifyShopCsvBossGates") { "pack/migrations/26.2.0-alpha.194-shop-csv-boss-gates-to-alpha.195.json")) def provider = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopCatalogProvider.java").text def catalog = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.pack_version != "26.2.0-alpha.195" || migration.target?.modules?.sanctuary != "0.0.0-alpha.111" || migration.compatibility?.sanctuary_data_version != 11 @@ -3189,7 +3197,7 @@ tasks.register("verifyDeliveryBoxOrientation") { def blockstate = new JsonSlurper().parse( file("src/main/resources/assets/sanctuary/blockstates/delivery_box.json")) def expectedVariants = ["facing=north", "facing=east", "facing=south", "facing=west"].toSet() - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.modules?.sanctuary != "0.0.0-alpha.112" || migration.mailbox?.id != "sanctuary:delivery_box" || migration.mailbox?.horizontal_facing != true @@ -3217,7 +3225,7 @@ tasks.register("verifyInventoryShiftClick") { def mixin = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AbstractContainerMenuMixin.java").text def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/AbstractContainerScreenMixin.java").text def gui = file("INVENTORY_GUI.md").text - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || !transfer.contains("shouldAppendOverflow") || !transfer.contains("isVanillaStorageRange") || !transfer.contains("hotbarStart") @@ -3263,7 +3271,7 @@ tasks.register("verifyQuestRequests") { new JsonSlurper().parse(file("src/main/resources/data/sanctuary/loot_table/blocks/${path}.json")) .pools[0].entries[0].name } - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.modules?.sanctuary != "0.0.0-alpha.113" || migration.quest_requests?.save_key != "quest_requests" || migration.quest_requests?.data_version != 1 @@ -3325,7 +3333,7 @@ tasks.register("verifyQuestRequestContracts") { def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale -> [(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))] } - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.198" || migration.source?.modules?.sanctuary != "0.0.0-alpha.114" || migration.target?.pack_version != "26.2.0-alpha.199" @@ -3394,7 +3402,7 @@ tasks.register("verifyQuestRequestEscrow") { def locales = ["fr_fr", "en_us", "ru_ru"].collectEntries { locale -> [(locale): new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${locale}.json"))] } - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.source?.pack_version != "26.2.0-alpha.200" || migration.source?.modules?.sanctuary != "0.0.0-alpha.115" || migration.target?.pack_version != "26.2.0-alpha.201" @@ -3473,7 +3481,7 @@ tasks.register("verifyCraftableBannerCape") { def locales = ["fr_fr", "en_us", "ru_ru"].collect { new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json")) } - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.modules?.sanctuary != "0.0.0-alpha.130" || migration.white_cape?.crafting_material_count != 6 || migration.white_cape?.maximum_pattern_layers != 6 @@ -3525,9 +3533,14 @@ tasks.register("verifyFinalSanctuaryTickets") { def locales = ["fr_fr", "en_us", "ru_ru"].collect { new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json")) } - if (project.version.toString() != "0.0.0-alpha.137" - || !capeRenderer.contains("SHIELD_PATTERN_X_SCALE = 1.2F") - || !capeRenderer.contains("SHIELD_PATTERN_Y_SCALE = 1.375F") + if (project.version.toString() != "0.0.0-alpha.138" + || !capeRenderer.contains("SHIELD_FACE_WIDTH = 12.0F") + || !capeRenderer.contains("SHIELD_FACE_HEIGHT = 22.0F") + || !capeRenderer.contains("CAPE_WIDTH / SHIELD_FACE_WIDTH") + || !capeRenderer.contains("CAPE_HEIGHT / SHIELD_FACE_HEIGHT") + || !capeRenderer.contains('addOrReplaceChild("front"') + || !capeRenderer.contains('addOrReplaceChild("back"') + || !capeRenderer.contains("Set.of(Direction.NORTH)") || !capeRenderer.contains("model, state, false, appearance.baseColor()") || !armorLayer.contains("EquipmentClientInfo.LayerType.WOLF_BODY") || !armorRules.contains("held.is(Items.WOLF_ARMOR)") @@ -3574,7 +3587,7 @@ tasks.register("verifyAlphaIndev") { def locales = ["fr_fr", "en_us", "ru_ru"].collect { new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json")) } - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.target?.modules?.sanctuary != "0.0.0-alpha.122" || migration.alpha_world?.dimension != "sanctuary:alpha" || migration.alpha_world?.legacy_dimension != "portals:alpha" @@ -3634,7 +3647,7 @@ tasks.register("verifyBackroom") { def locales = ["fr_fr", "en_us", "ru_ru"].collect { new JsonSlurper().parse(file("src/main/resources/assets/sanctuary/lang/${it}.json")) } - if (project.version.toString() != "0.0.0-alpha.137" + if (project.version.toString() != "0.0.0-alpha.138" || migration.source_data_version != 13 || migration.target_data_version != 14 || migration.integration_modules?.anotherworld?.source != "0.0.0-alpha.56" || migration.integration_modules?.anotherworld?.target != "0.0.0-alpha.57" @@ -3696,6 +3709,7 @@ tasks.named("check") { dependsOn tasks.named("alphaIndevGeneratorSmoke") dependsOn tasks.named("capeJoinSyncSmoke") dependsOn tasks.named("capeSyncPayloadSmoke") + dependsOn tasks.named("capeModelSmoke") dependsOn tasks.named("chickenArmorSmoke") dependsOn tasks.named("inventorySortSmoke") dependsOn tasks.named("entityPolicySmoke") diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/RankingSnapshot.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/RankingSnapshot.java index 31a2af5..8b7f46a 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/RankingSnapshot.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/RankingSnapshot.java @@ -4,9 +4,9 @@ import java.util.List; import java.util.UUID; import net.minecraft.resources.Identifier; -/** Server snapshot intentionally bounded to one visible metric and three podium entries. */ +/** Server snapshot intentionally bounded to one visible metric and seven panel rows. */ public record RankingSnapshot(RankingMetric metric, List podium, Placement owner) { - public static final int MAX_PODIUM_SIZE = 3; + public static final int MAX_PODIUM_SIZE = 7; public RankingSnapshot { podium = podium == null ? List.of() : List.copyOf(podium.stream().limit(MAX_PODIUM_SIZE).toList()); diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/SanctuaryRankingApi.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/SanctuaryRankingApi.java index e2b25f4..53a89a9 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/SanctuaryRankingApi.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/SanctuaryRankingApi.java @@ -8,6 +8,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -66,6 +67,11 @@ public final class SanctuaryRankingApi { return metrics; } + /** Localized label for a metric configured from its representative item. */ + public static Component displayName(RankingMetric metric, ItemStack icon) { + return VanillaRankingMetric.displayName(metric, icon); + } + public static boolean eligible(ServerPlayer player) { return isRealOnlinePlayer(player) && !player.isCreative() && !player.isSpectator(); } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/VanillaRankingMetric.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/VanillaRankingMetric.java index cf9329a..1898260 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/VanillaRankingMetric.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/api/ranking/VanillaRankingMetric.java @@ -5,12 +5,14 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerPlayer; import net.minecraft.stats.Stats; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.SpawnEggItem; /** Stable encoding and server reader for persisted vanilla statistic identifiers. */ final class VanillaRankingMetric { @@ -43,6 +45,13 @@ final class VanillaRankingMetric { static List itemMetrics(ItemStack icon) { if (icon == null || icon.isEmpty()) return List.of(); Item item = icon.getItem(); + if (item instanceof SpawnEggItem) { + var entityType = SpawnEggItem.getType(icon); + if (entityType == null) return List.of(); + Identifier entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entityType); + return List.of(new RankingMetric(id(Identifier.withDefaultNamespace("killed"), entityId), + "metric.sanctuary.ranking.killed", RankingUnit.COUNT)); + } Identifier itemId = BuiltInRegistries.ITEM.getKey(item); List metrics = new ArrayList<>(); for (String family : ITEM_FAMILIES) { @@ -53,6 +62,18 @@ final class VanillaRankingMetric { return List.copyOf(metrics); } + static Component displayName(RankingMetric metric, ItemStack icon) { + Source source = parse(metric.id()).orElse(null); + if (source != null && source.type().getPath().equals("killed")) { + Component target = BuiltInRegistries.ENTITY_TYPE.getOptional(source.target()) + .map(type -> type.getDescription()).orElseGet(() -> iconName(icon)); + return Component.translatable(metric.translationKey(), target); + } + return icon == null || icon.isEmpty() + ? Component.translatable(metric.translationKey()) + : Component.translatable(metric.translationKey(), icon.getHoverName()); + } + static Optional parse(Identifier metricId) { if (!metricId.getNamespace().equals(SanctuaryMod.MOD_ID) || !metricId.getPath().startsWith(PREFIX)) return Optional.empty(); @@ -79,10 +100,16 @@ final class VanillaRankingMetric { .map(item -> (long) player.getStats().getValue(Stats.ITEM_PICKED_UP, item)).orElse(0L); case "dropped" -> BuiltInRegistries.ITEM.getOptional(target) .map(item -> (long) player.getStats().getValue(Stats.ITEM_DROPPED, item)).orElse(0L); + case "killed" -> BuiltInRegistries.ENTITY_TYPE.getOptional(target) + .map(type -> (long) player.getStats().getValue(Stats.ENTITY_KILLED, type)).orElse(0L); case "custom" -> (long) player.getStats().getValue(Stats.CUSTOM, target); default -> 0L; }; } + private static Component iconName(ItemStack icon) { + return icon == null || icon.isEmpty() ? Component.literal("?") : icon.getHoverName(); + } + record Source(Identifier type, Identifier target) {} } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java index f255415..e6ca0b7 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java @@ -2,6 +2,7 @@ package fr.koka99cab.sanctuary26.sanctuary.client; import com.mojang.blaze3d.vertex.PoseStack; import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod; +import java.util.Set; import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityRenderLayerRegistrationCallback; import net.fabricmc.fabric.api.client.rendering.v1.ModelLayerRegistry; import net.minecraft.client.model.geom.ModelLayerLocation; @@ -23,6 +24,7 @@ import net.minecraft.client.renderer.entity.state.AvatarRenderState; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.resources.model.EquipmentAssetManager; import net.minecraft.client.resources.model.EquipmentClientInfo; +import net.minecraft.core.Direction; import net.minecraft.core.component.DataComponents; import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.item.ItemStack; @@ -30,8 +32,10 @@ import net.minecraft.world.item.equipment.Equippable; /** Renders a banner-decorated cape with vanilla cape physics and banner pattern sprites. */ public final class SanctuaryBannerCapeLayer extends RenderLayer { - private static final float SHIELD_PATTERN_X_SCALE = 1.2F; - private static final float SHIELD_PATTERN_Y_SCALE = 1.375F; + private static final float SHIELD_FACE_WIDTH = 12.0F; + private static final float SHIELD_FACE_HEIGHT = 22.0F; + private static final float CAPE_WIDTH = 10.0F; + private static final float CAPE_HEIGHT = 16.0F; private static final ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(SanctuaryMod.id("banner_cape"), "main"); private final PlayerCapeModel model; @@ -63,17 +67,27 @@ public final class SanctuaryBannerCapeLayer extends RenderLayer zombieMetrics = VanillaRankingMetric.itemMetrics( + new ItemStack(new Holder.Direct(Items.ZOMBIE_SPAWN_EGG, DataComponentMap.builder() + .set(DataComponents.MAX_STACK_SIZE, 64) + .set(DataComponents.ENTITY_DATA, TypedEntityData.of(EntityTypes.ZOMBIE, new CompoundTag())) + .build()))); + VanillaRankingMetric.Source zombieSource = VanillaRankingMetric.parse( + zombieMetrics.getFirst().id()).orElseThrow(); + require(zombieMetrics.size() == 1 && zombieSource.type().getPath().equals("killed") + && zombieSource.target().equals(Identifier.withDefaultNamespace("zombie")), + "A zombie spawn egg must configure the zombies-killed ranking"); Path temporary = Files.createTempDirectory("sanctuary-ranking-migration-"); Path oldStats = temporary.resolve(alex + ".json"); diff --git a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeModelSmoke.java b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeModelSmoke.java new file mode 100644 index 0000000..fbe6aa9 --- /dev/null +++ b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeModelSmoke.java @@ -0,0 +1,80 @@ +package fr.koka99cab.sanctuary26.sanctuary.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.client.model.geom.ModelPart; +import org.joml.Vector3f; +import org.joml.Vector3fc; + +/** Verifies the shield-pattern UV island, cape proportions and double-sided plane. */ +public final class SanctuaryBannerCapeModelSmoke { + private static final float EPSILON = 0.0001F; + + private SanctuaryBannerCapeModelSmoke() {} + + public static void main(String[] args) { + ModelPart cape = SanctuaryBannerCapeLayer.createLayer().bakeRoot() + .getChild("body").getChild("cape"); + List normalZ = new ArrayList<>(); + List vertices = new ArrayList<>(); + cape.visit(new PoseStack(), (pose, path, cubeIndex, cube) -> { + for (ModelPart.Polygon polygon : cube.polygons) { + normalZ.add(pose.transformNormal(polygon.normal(), new Vector3f()).z()); + vertices.addAll(List.of(polygon.vertices())); + } + }); + require(normalZ.size() == 2 && normalZ.stream().anyMatch(value -> value < -0.99F) + && normalZ.stream().anyMatch(value -> value > 0.99F), + "The banner cape must contain one visible plane for each culling direction"); + require(close(minU(vertices), 1.0F / 64.0F) && close(maxU(vertices), 13.0F / 64.0F) + && close(minV(vertices), 1.0F / 64.0F) && close(maxV(vertices), 23.0F / 64.0F), + "The cape no longer samples the shield plate's exact pattern UV island"); + + List extents = new ArrayList<>(); + cape.getExtentsForGui(new PoseStack(), extents::add); + require(close(minX(extents), -5.0F / 16.0F) && close(maxX(extents), 5.0F / 16.0F) + && close(minY(extents), 0.0F) && close(maxY(extents), 1.0F), + "The shield-pattern plane does not retain the vanilla 10x16 cape proportions"); + } + + private static float minU(List vertices) { + return (float) vertices.stream().mapToDouble(ModelPart.Vertex::u).min().orElseThrow(); + } + + private static float maxU(List vertices) { + return (float) vertices.stream().mapToDouble(ModelPart.Vertex::u).max().orElseThrow(); + } + + private static float minV(List vertices) { + return (float) vertices.stream().mapToDouble(ModelPart.Vertex::v).min().orElseThrow(); + } + + private static float maxV(List vertices) { + return (float) vertices.stream().mapToDouble(ModelPart.Vertex::v).max().orElseThrow(); + } + + private static float minX(List vertices) { + return (float) vertices.stream().mapToDouble(Vector3fc::x).min().orElseThrow(); + } + + private static float maxX(List vertices) { + return (float) vertices.stream().mapToDouble(Vector3fc::x).max().orElseThrow(); + } + + private static float minY(List vertices) { + return (float) vertices.stream().mapToDouble(Vector3fc::y).min().orElseThrow(); + } + + private static float maxY(List vertices) { + return (float) vertices.stream().mapToDouble(Vector3fc::y).max().orElseThrow(); + } + + private static boolean close(float actual, float expected) { + return Math.abs(actual - expected) <= EPSILON; + } + + private static void require(boolean condition, String message) { + if (!condition) throw new AssertionError(message); + } +} -- 2.54.0