diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed39d9..1a52a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve ## Non publié -Modules modifiés : **Sanctuary `0.0.0-alpha.135`** et **Red-Stoner `0.0.0-alpha.17`**. +Modules modifiés : **Sanctuary `0.0.0-alpha.136`** et **Red-Stoner `0.0.0-alpha.17`**. - attribue les demandes de quêtes personnalisées terminées à leur preneur directement sur le panneau et dans les détails au clic, avec son pseudonyme et sa couleur conservés ; - répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom, avec exactement un lit par cellule de 10 × 10 chunks et par niveau dans les nouveaux chunks ; @@ -16,6 +16,9 @@ Modules modifiés : **Sanctuary `0.0.0-alpha.135`** et **Red-Stoner `0.0.0-alpha - ajoute l’API publique Sanctuary de métriques et de classements persistants par UUID, avec import monotone des statistiques vanilla, podium borné et exclusion des actions créatives, spectateur ou simulées ; - ajoute `redstoner:trophy_panel`, panneau mural configurable par son propriétaire avec un item non consommé, pages publiques, rendu en monde et rafraîchissement serveur limité à une fois par seconde ; - passe Sanctuary aux données v15 et Red-Stoner aux données v4 sans nouveau payload ni changement de leurs protocoles réseau 24 et 7. +- corrige la cape blanche décorée en projetant sa couleur et ses motifs avec les sprites de bouclier, aux proportions exactes de la cape ; +- permet d’équiper une poule adulte avec l’armure de loup vanilla en écailles de tatou, de la réparer avec des écailles et de la retirer aux cisailles ; l’armure absorbe les dégâts et conserve son usure, ses teintures et ses enchantements ; +- conserve les sauvegardes Sanctuary v15 et le protocole réseau 24 : l’armure utilise exclusivement le slot d’équipement vanilla de la poule. ## `26.2.0-alpha.229` diff --git a/README.md b/README.md index e4aa28b..3f49e85 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Le prochain module Sanctuary **`0.0.0-alpha.134`** attribue les quêtes communau Sanctuary **`0.0.0-alpha.135`** et Red-Stoner **`0.0.0-alpha.17`** ajoutent les classements publics et le panneau mural `redstoner:trophy_panel`. Les statistiques vanilla des anciens joueurs sont importées une fois, les scores restent dans Sanctuary et le panneau ne reçoit que sa page visible ainsi qu’un podium de trois noms maximum. Les données passent respectivement à v15 et v4 sans changer les protocoles réseau 24 et 7. +Sanctuary **`0.0.0-alpha.136`** corrige la projection des couleurs et motifs de la cape blanche en utilisant les sprites de bouclier. L’armure de loup vanilla en écailles de tatou peut désormais équiper une poule adulte : elle absorbe les dégâts, s’use, se répare avec des écailles et se retire aux cisailles sans nouveau format de sauvegarde. + L’alpha.229 ajoute au terminal les tris alphabétique, par quantité croissante et par quantité décroissante. Le tri ne change que sur demande explicite et les rafraîchissements périodiques conservent l’ordre ainsi que les objets sous le curseur. Les sauvegardes, les données Red-Stoner v3 et le protocole réseau 7 restent compatibles. L’alpha.228 reconstruit la Backroom sur cinq niveaux empilés entre Y -64 et Y 383. Le monde devient entièrement noir et sans monstres ; l’arrivée est signalée par une torche récupérable. Les nouveaux chunks composent seuils modulaires, bibliothèques et bureaux, ville dense avec autoroutes et parcs, couloirs techniques, puis une grille 3D et un labyrinthe connecté dont certains trous mènent au vide. Red-Stoner corrige aussi le focus du terminal et permet de retrouver une boîte de Shulker par son contenu. Les chunks déjà générés et les sauvegardes existantes restent compatibles. diff --git a/build.gradle b/build.gradle index 855b510..a9785a2 100644 --- a/build.gradle +++ b/build.gradle @@ -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.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || rootProject.masterkey_version != "0.0.0-alpha.9" || rootProject.pack_version != "26.2.0-alpha.229" || manifests.itsalive.custom?.sanctuary26?.data_version != 4 @@ -3010,7 +3010,7 @@ tasks.register("verifyMagicCarpet") { if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.iliketomoveit_version != "0.0.0-alpha.18" || rootProject.onlyfun_version != "0.0.0-alpha.27" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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.229" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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"] @@ -4240,7 +4240,7 @@ project(":redstoner") { "sanctuary/src/main/resources/assets/sanctuary/lang/${it}.json")) } if (rootProject.redstoner_version != "0.0.0-alpha.17" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || redstonerManifest.custom?.sanctuary26?.data_version != 4 || redstonerManifest.custom?.sanctuary26?.network_protocol != 7 || sanctuaryManifest.custom?.sanctuary26?.data_version != 15 @@ -4576,7 +4576,7 @@ tasks.register("verifyShopPricingLayoutRelease") { def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.onlyfun_version != "0.0.0-alpha.27" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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" @@ -4670,7 +4670,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.229" || rootProject.onlyfun_version != "0.0.0-alpha.27" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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"] @@ -4763,7 +4763,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.229" || rootProject.itsalive_version != "0.0.0-alpha.38" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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"] @@ -4863,7 +4863,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.229" || rootProject.itsalive_version != "0.0.0-alpha.38" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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" @@ -4983,7 +4983,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.229" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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" @@ -5040,7 +5040,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.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.source?.pack_version != "26.2.0-alpha.201" || migration.source?.modules != [ itsalive: "0.0.0-alpha.30", @@ -5198,7 +5198,7 @@ tasks.register("verifySafeVaultFeature") { }.keySet() if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.anotherworld_version != "0.0.0-alpha.57" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.schema_version != 1 || migration.source?.pack_version != "26.2.0-alpha.201" || migration.target?.pack_version != "26.2.0-alpha.202" @@ -6058,7 +6058,7 @@ tasks.register("verifyAlpha210Release") { it.path == "mods/jei-26.2-fabric-30.29.0.199.jar" } if (rootProject.pack_version != "26.2.0-alpha.229" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || rootProject.onlyfun_version != "0.0.0-alpha.27" || rootProject.iliketomoveit_version != "0.0.0-alpha.18" || rootProject.itsalive_version != "0.0.0-alpha.38" @@ -6160,7 +6160,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.135"] + def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.136"] 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] @@ -6260,7 +6260,7 @@ tasks.register("verifyAlpha212Release") { it.path == "mods/jei-26.2-fabric-30.29.0.199.jar" } if (rootProject.pack_version != "26.2.0-alpha.229" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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" @@ -6759,7 +6759,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.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.source?.pack_version != "26.2.0-alpha.217" || migration.source?.modules != sourceModules || migration.target?.pack_version != "26.2.0-alpha.218" @@ -6845,7 +6845,7 @@ tasks.register("verifyAlpha219Release") { ambiance: "0.0.0-alpha.22", sanctuary: "0.0.0-alpha.132", 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.135"] + def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.136"] def requiredJars = currentModules.collect { id, version -> "file = \"mods/${id}-${version}.jar\"" } @@ -7170,7 +7170,7 @@ tasks.register("verifyAlpha222Release") { } if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.redstoner_version != "0.0.0-alpha.17" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.source?.pack_version != "26.2.0-alpha.221" || migration.source?.modules != [ redstoner: "0.0.0-alpha.12", sanctuary: "0.0.0-alpha.122"] @@ -7382,7 +7382,7 @@ tasks.register("verifyAlpha223Release") { it.path == "mods/jei-26.2-fabric-30.29.0.199.jar" } if (rootProject.pack_version != "26.2.0-alpha.229" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || 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" @@ -7503,7 +7503,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.132"] - def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.135"] + def workspaceModules = currentModules + [sanctuary: "0.0.0-alpha.136"] def inventorySort = file( "sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryInventorySort.java").text def clientProgress = file( @@ -7625,7 +7625,7 @@ tasks.register("verifyAlpha225Release") { sanctuary: "0.0.0-alpha.129"] def currentModules = targetModules + [sanctuary: "0.0.0-alpha.132"] def currentWorkspaceModules = currentModules + [ - anotherworld: "0.0.0-alpha.57", sanctuary: "0.0.0-alpha.135"] + anotherworld: "0.0.0-alpha.57", sanctuary: "0.0.0-alpha.136"] def workspaceModules = moduleIds.collectEntries { id -> [(id): project(":${id}").version.toString()] } @@ -7796,7 +7796,7 @@ tasks.register("verifyAlpha226Release") { "file = \"mods/${id}-${version}.jar\"" } if (rootProject.pack_version != "26.2.0-alpha.229" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.225" || migration.source?.modules != [sanctuary: "0.0.0-alpha.129"] @@ -7889,7 +7889,7 @@ tasks.register("verifyAlpha227Release") { } if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.anotherworld_version != "0.0.0-alpha.57" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.226" || migration.source?.modules != [ @@ -7988,7 +7988,7 @@ tasks.register("verifyAlpha228Release") { } if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.redstoner_version != "0.0.0-alpha.17" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.227" || migration.source?.modules != [ @@ -8099,7 +8099,7 @@ tasks.register("verifyAlpha229Release") { "screen.redstoner.storage_terminal.sort.count_descending"] if (rootProject.pack_version != "26.2.0-alpha.229" || rootProject.redstoner_version != "0.0.0-alpha.17" - || rootProject.sanctuary_version != "0.0.0-alpha.135" + || rootProject.sanctuary_version != "0.0.0-alpha.136" || migration.status != "released" || migration.source?.pack_version != "26.2.0-alpha.228" || migration.source?.modules != [redstoner: "0.0.0-alpha.15"] diff --git a/gradle.properties b/gradle.properties index c5cc412..cc83ea1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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.135 +sanctuary_version=0.0.0-alpha.136 sanctuary_lifecycle=active pack_version=26.2.0-alpha.229 maven_group=fr.koka99cab.sanctuary26 diff --git a/sanctuary/build.gradle b/sanctuary/build.gradle index 0dd8ff7..24be63c 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("chickenArmorSmoke", JavaExec) { + group = "verification" + description = "Checks that only adult unarmored chickens accept armadillo-scute armor." + dependsOn tasks.named("testClasses") + classpath = sourceSets.test.runtimeClasspath + mainClass = "fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryChickenArmorSmoke" +} + tasks.register("inventorySortSmoke", JavaExec) { group = "verification" description = "Checks that storage stacks merge and sort while the hotbar row stays out of the sort." @@ -199,7 +207,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || !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") @@ -2915,7 +2923,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || 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 @@ -3081,7 +3089,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || 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 @@ -3119,7 +3127,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || 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] @@ -3150,7 +3158,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || migration.target?.pack_version != "26.2.0-alpha.195" || migration.target?.modules?.sanctuary != "0.0.0-alpha.111" || migration.compatibility?.sanctuary_data_version != 11 @@ -3181,7 +3189,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || migration.target?.modules?.sanctuary != "0.0.0-alpha.112" || migration.mailbox?.id != "sanctuary:delivery_box" || migration.mailbox?.horizontal_facing != true @@ -3209,7 +3217,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || !transfer.contains("shouldAppendOverflow") || !transfer.contains("isVanillaStorageRange") || !transfer.contains("hotbarStart") @@ -3255,7 +3263,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || migration.target?.modules?.sanctuary != "0.0.0-alpha.113" || migration.quest_requests?.save_key != "quest_requests" || migration.quest_requests?.data_version != 1 @@ -3317,7 +3325,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || 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" @@ -3386,7 +3394,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || 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" @@ -3465,7 +3473,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || migration.target?.modules?.sanctuary != "0.0.0-alpha.130" || migration.white_cape?.crafting_material_count != 6 || migration.white_cape?.maximum_pattern_layers != 6 @@ -3493,6 +3501,52 @@ tasks.named("verifySanctuary") { dependsOn tasks.named("verifyCraftableBannerCape") } +tasks.register("verifyFinalSanctuaryTickets") { + group = "verification" + description = "Checks the shield-mapped banner cape and armadillo-scute chicken armor contracts." + inputs.files( + file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java"), + file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorLayer.java"), + file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java"), + file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AnimalChickenArmorMixin.java"), + file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java"), + file("src/main/resources/sanctuary.mixins.json")) + inputs.files(fileTree("src/main/resources/assets/sanctuary/lang") { include "*.json" }) + doLast { + def capeRenderer = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java").text + def armorLayer = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorLayer.java").text + def armorRules = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java").text + def armorDamage = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java").text + def mixins = new JsonSlurper().parse(file("src/main/resources/sanctuary.mixins.json")) + 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.136" + || !capeRenderer.contains("SHIELD_PATTERN_X_SCALE = 1.2F") + || !capeRenderer.contains("SHIELD_PATTERN_Y_SCALE = 1.375F") + || !capeRenderer.contains("model, state, false, appearance.baseColor()") + || !armorLayer.contains("EquipmentClientInfo.LayerType.WOLF_BODY") + || !armorRules.contains("held.is(Items.WOLF_ARMOR)") + || !armorRules.contains("DamageTypeTags.BYPASSES_WOLF_ARMOR") + || !armorDamage.contains("armor.hurtAndBreak") + || !mixins.mixins.contains("AnimalChickenArmorMixin") + || !mixins.mixins.contains("LivingEntityChickenArmorMixin") + || !mixins.client.contains("client.ChickenRendererMixin") + || !mixins.client.contains("client.ChickenRenderStateMixin") + || locales.any { !it.containsKey("tooltip.sanctuary.chicken_armor") }) { + throw new GradleException("Sanctuary alpha.136 final-ticket contract is incomplete") + } + } +} + +tasks.named("verifySanctuary") { + dependsOn tasks.named("verifyFinalSanctuaryTickets") +} + tasks.register("verifyAlphaIndev") { group = "verification" description = "Checks the alpha.122 finite Indev world, migration and attribution contract." @@ -3520,7 +3574,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || migration.target?.modules?.sanctuary != "0.0.0-alpha.122" || migration.alpha_world?.dimension != "sanctuary:alpha" || migration.alpha_world?.legacy_dimension != "portals:alpha" @@ -3580,7 +3634,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.135" + if (project.version.toString() != "0.0.0-alpha.136" || 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" @@ -3640,6 +3694,7 @@ tasks.named("check") { dependsOn tasks.named("alphaIndevGeneratorSmoke") dependsOn tasks.named("capeJoinSyncSmoke") dependsOn tasks.named("capeSyncPayloadSmoke") + dependsOn tasks.named("chickenArmorSmoke") dependsOn tasks.named("inventorySortSmoke") dependsOn tasks.named("entityPolicySmoke") dependsOn tasks.named("verifyAlphaIndev") 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 e991735..f255415 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 @@ -30,6 +30,8 @@ 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 ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(SanctuaryMod.id("banner_cape"), "main"); private final PlayerCapeModel model; @@ -67,7 +69,7 @@ public final class SanctuaryBannerCapeLayer extends RenderLayer { + private static final ModelLayerLocation MODEL_LAYER = + new ModelLayerLocation(SanctuaryMod.id("chicken_armor"), "main"); + private final SanctuaryChickenArmorModel model; + private final EquipmentLayerRenderer equipmentRenderer; + + private SanctuaryChickenArmorLayer(RenderLayerParent parent, + EntityRendererProvider.Context context) { + super(parent); + model = SanctuaryChickenArmorModel.bake(context.bakeLayer(MODEL_LAYER)); + equipmentRenderer = context.getEquipmentRenderer(); + } + + public static void initialize() { + ModelLayerRegistry.registerModelLayer(MODEL_LAYER, SanctuaryChickenArmorModel::createLayer); + LivingEntityRenderLayerRegistrationCallback.EVENT.register((entityType, renderer, helper, context) -> { + if (entityType == EntityTypes.CHICKEN && renderer instanceof ChickenRenderer chickenRenderer) { + register(helper, chickenRenderer, context); + } + }); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static void register(LivingEntityRenderLayerRegistrationCallback.RegistrationHelper helper, + ChickenRenderer renderer, EntityRendererProvider.Context context) { + RenderLayerParent parent = + (RenderLayerParent) (RenderLayerParent) renderer; + helper.register(new SanctuaryChickenArmorLayer(parent, context)); + } + + @Override + public void submit(PoseStack poseStack, SubmitNodeCollector collector, int packedLight, + ChickenRenderState state, float yRot, float xRot) { + if (state.isBaby || state.isInvisibleToPlayer) return; + ItemStack armor = ((SanctuaryChickenArmorRenderState) state).sanctuary$chickenArmor(); + Equippable equippable = armor.get(DataComponents.EQUIPPABLE); + if (equippable == null || equippable.assetId().isEmpty()) return; + equipmentRenderer.renderLayers(EquipmentClientInfo.LayerType.WOLF_BODY, + equippable.assetId().get(), model, state, armor, poseStack, collector, + packedLight, state.outlineColor); + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorModel.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorModel.java new file mode 100644 index 0000000..0bafa17 --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorModel.java @@ -0,0 +1,32 @@ +package fr.koka99cab.sanctuary26.sanctuary.client; + +import net.minecraft.client.model.EntityModel; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.CubeDeformation; +import net.minecraft.client.model.geom.builders.CubeListBuilder; +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.model.geom.builders.MeshDefinition; +import net.minecraft.client.renderer.entity.state.ChickenRenderState; + +/** A thin torso shell using the vanilla armadillo-scute equipment UVs. */ +public final class SanctuaryChickenArmorModel extends EntityModel { + private SanctuaryChickenArmorModel(ModelPart root) { + super(root); + } + + public static SanctuaryChickenArmorModel bake(ModelPart root) { + return new SanctuaryChickenArmorModel(root); + } + + public static LayerDefinition createLayer() { + MeshDefinition mesh = new MeshDefinition(); + mesh.getRoot().addOrReplaceChild("body", + CubeListBuilder.create().texOffs(18, 14) + .addBox(-3.0F, -4.0F, -3.0F, 6.0F, 8.0F, 6.0F, + new CubeDeformation(0.45F)), + PartPose.offsetAndRotation(0.0F, 16.0F, 0.0F, + (float) (Math.PI / 2.0D), 0.0F, 0.0F)); + return LayerDefinition.create(mesh, 64, 32); + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorRenderState.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorRenderState.java new file mode 100644 index 0000000..ec1afbd --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryChickenArmorRenderState.java @@ -0,0 +1,10 @@ +package fr.koka99cab.sanctuary26.sanctuary.client; + +import net.minecraft.world.item.ItemStack; + +/** Mixin-backed bridge carrying chicken body armor into the extracted render state. */ +public interface SanctuaryChickenArmorRenderState { + ItemStack sanctuary$chickenArmor(); + + void sanctuary$setChickenArmor(ItemStack armor); +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryClient.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryClient.java index 3785995..da6d409 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryClient.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryClient.java @@ -120,6 +120,7 @@ public final class SanctuaryClient implements ClientModInitializer { SanctuaryHealthPreview.register(); CelestialSpyglassSelection.register(); SanctuaryBannerCapeLayer.initialize(); + SanctuaryChickenArmorLayer.initialize(); EntityRendererRegistry.register(SanctuaryRegistries.SEAT, NoopRenderer::new); ClientTickEvents.END_CLIENT_TICK.register(SanctuaryClient::tick); ClientPlayNetworking.registerGlobalReceiver(ProgressionSnapshotPayload.ID, (payload, context) -> diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java new file mode 100644 index 0000000..53ad21e --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmor.java @@ -0,0 +1,25 @@ +package fr.koka99cab.sanctuary26.sanctuary.gameplay; + +import net.minecraft.tags.DamageTypeTags; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; + +/** Shared server rules for fitting vanilla armadillo-scute armor to adult chickens. */ +public final class SanctuaryChickenArmor { + private SanctuaryChickenArmor() { + } + + public static boolean canEquip(ItemStack equipped, ItemStack held, boolean baby) { + return !baby && equipped.isEmpty() && held.is(Items.WOLF_ARMOR); + } + + public static boolean canRepair(ItemStack equipped, ItemStack held) { + return equipped.is(Items.WOLF_ARMOR) && equipped.isDamaged() + && equipped.isValidRepairItem(held); + } + + public static boolean absorbs(ItemStack equipped, DamageSource source) { + return equipped.is(Items.WOLF_ARMOR) && !source.is(DamageTypeTags.BYPASSES_WOLF_ARMOR); + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AnimalChickenArmorMixin.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AnimalChickenArmorMixin.java new file mode 100644 index 0000000..f938d19 --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/AnimalChickenArmorMixin.java @@ -0,0 +1,43 @@ +package fr.koka99cab.sanctuary26.sanctuary.mixin; + +import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryChickenArmor; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.chicken.Chicken; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** Lets adult chickens wear and repair the vanilla armadillo-scute wolf armor. */ +@Mixin(Animal.class) +abstract class AnimalChickenArmorMixin { + @Inject(method = "mobInteract", at = @At("HEAD"), cancellable = true) + private void sanctuary$handleChickenArmor(Player player, InteractionHand hand, + CallbackInfoReturnable cir) { + if (!((Object) this instanceof Chicken chicken)) return; + ItemStack held = player.getItemInHand(hand); + ItemStack equipped = chicken.getItemBySlot(EquipmentSlot.BODY); + if (SanctuaryChickenArmor.canEquip(equipped, held, chicken.isBaby())) { + chicken.setItemSlot(EquipmentSlot.BODY, held.copyWithCount(1)); + chicken.setGuaranteedDrop(EquipmentSlot.BODY); + held.consume(1, player); + chicken.playSound(SoundEvents.ARMOR_EQUIP_WOLF.value()); + cir.setReturnValue(InteractionResult.SUCCESS); + return; + } + if (SanctuaryChickenArmor.canRepair(equipped, held)) { + held.consume(1, player); + int repairUnit = Mth.ceil(equipped.getMaxDamage() * 0.125F); + equipped.setDamageValue(Math.max(0, equipped.getDamageValue() - repairUnit)); + chicken.playSound(SoundEvents.WOLF_ARMOR_REPAIR); + cir.setReturnValue(InteractionResult.SUCCESS); + } + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/ItemTooltipMixin.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/ItemTooltipMixin.java index a467c93..542b2e9 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/ItemTooltipMixin.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/ItemTooltipMixin.java @@ -2,9 +2,11 @@ package fr.koka99cab.sanctuary26.sanctuary.mixin; import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryCompanionPowers; import java.util.function.Consumer; +import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.SpawnEggItem; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.item.component.TooltipDisplay; @@ -21,5 +23,9 @@ abstract class ItemTooltipMixin { if (stack.getItem() instanceof SpawnEggItem) { SanctuaryCompanionPowers.appendPowerTooltip(stack, tooltip); } + if (stack.is(Items.WOLF_ARMOR)) { + tooltip.accept(Component.translatable("tooltip.sanctuary.chicken_armor") + .withStyle(ChatFormatting.GRAY)); + } } } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java new file mode 100644 index 0000000..a9ddf9e --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/LivingEntityChickenArmorMixin.java @@ -0,0 +1,44 @@ +package fr.koka99cab.sanctuary26.sanctuary.mixin; + +import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryChickenArmor; +import net.minecraft.core.particles.ItemParticleOption; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Crackiness; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.chicken.Chicken; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** Gives armored chickens the same damage-absorption contract as armored wolves. */ +@Mixin(LivingEntity.class) +abstract class LivingEntityChickenArmorMixin { + @Inject(method = "actuallyHurt", at = @At("HEAD"), cancellable = true) + private void sanctuary$absorbChickenDamage(ServerLevel level, DamageSource source, + float damage, CallbackInfo ci) { + if (!((Object) this instanceof Chicken chicken)) return; + ItemStack armor = chicken.getItemBySlot(EquipmentSlot.BODY); + if (!SanctuaryChickenArmor.absorbs(armor, source)) return; + + int damageBefore = armor.getDamageValue(); + int maxDamage = armor.getMaxDamage(); + armor.hurtAndBreak(Mth.ceil(damage), chicken, EquipmentSlot.BODY); + chicken.playSound(SoundEvents.WOLF_ARMOR_DAMAGE); + if (Crackiness.WOLF_ARMOR.byDamage(damageBefore, maxDamage) + != Crackiness.WOLF_ARMOR.byDamage(chicken.getItemBySlot(EquipmentSlot.BODY))) { + chicken.playSound(SoundEvents.WOLF_ARMOR_CRACK); + level.sendParticles(new ItemParticleOption(ParticleTypes.ITEM, + Items.ARMADILLO_SCUTE), chicken.getX(), chicken.getY() + 0.5D, + chicken.getZ(), 12, 0.2D, 0.15D, 0.2D, 0.08D); + } + ci.cancel(); + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/ChickenRenderStateMixin.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/ChickenRenderStateMixin.java new file mode 100644 index 0000000..78d5126 --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/ChickenRenderStateMixin.java @@ -0,0 +1,23 @@ +package fr.koka99cab.sanctuary26.sanctuary.mixin.client; + +import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryChickenArmorRenderState; +import net.minecraft.client.renderer.entity.state.ChickenRenderState; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +/** Stores synchronized chicken body armor on the client render state. */ +@Mixin(ChickenRenderState.class) +abstract class ChickenRenderStateMixin implements SanctuaryChickenArmorRenderState { + @Unique private ItemStack sanctuary$chickenArmor = ItemStack.EMPTY; + + @Override + public ItemStack sanctuary$chickenArmor() { + return sanctuary$chickenArmor; + } + + @Override + public void sanctuary$setChickenArmor(ItemStack armor) { + sanctuary$chickenArmor = armor == null ? ItemStack.EMPTY : armor; + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/ChickenRendererMixin.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/ChickenRendererMixin.java new file mode 100644 index 0000000..1f18541 --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/ChickenRendererMixin.java @@ -0,0 +1,22 @@ +package fr.koka99cab.sanctuary26.sanctuary.mixin.client; + +import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryChickenArmorRenderState; +import net.minecraft.client.renderer.entity.ChickenRenderer; +import net.minecraft.client.renderer.entity.state.ChickenRenderState; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.animal.chicken.Chicken; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** Copies vanilla-synchronized body equipment into each chicken render state. */ +@Mixin(ChickenRenderer.class) +abstract class ChickenRendererMixin { + @Inject(method = "extractRenderState", at = @At("TAIL")) + private void sanctuary$extractChickenArmor(Chicken chicken, ChickenRenderState state, + float partialTick, CallbackInfo ci) { + ((SanctuaryChickenArmorRenderState) state).sanctuary$setChickenArmor( + chicken.getItemBySlot(EquipmentSlot.BODY).copy()); + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json b/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json index 63b06ce..ebeca00 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json +++ b/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json @@ -517,6 +517,7 @@ "item.sanctuary.mojang_classic_cape": "Mojang Classic Cape", "item.sanctuary.blue_scroll_cape": "Blue Scroll Cape", "item.sanctuary.white_cape": "White Cape", + "tooltip.sanctuary.chicken_armor": "Can also equip and protect an adult chicken.", "dimension.sanctuary.alpha": "Alpha / Indev Island", "dimension.sanctuary.backroom": "Backroom", "message.sanctuary.backroom.unavailable": "The Backroom is unavailable in this world.", diff --git a/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json b/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json index 8584ddc..db2b110 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json +++ b/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json @@ -517,6 +517,7 @@ "item.sanctuary.mojang_classic_cape": "Cape Mojang classique", "item.sanctuary.blue_scroll_cape": "Cape parchemin bleu", "item.sanctuary.white_cape": "Cape blanche", + "tooltip.sanctuary.chicken_armor": "Peut aussi équiper et protéger une poule adulte.", "dimension.sanctuary.alpha": "Île Alpha / Indev", "dimension.sanctuary.backroom": "Backroom", "message.sanctuary.backroom.unavailable": "La Backroom n’est pas disponible dans ce monde.", diff --git a/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json b/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json index cf2304c..2bf800c 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json +++ b/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json @@ -473,6 +473,7 @@ "item.sanctuary.mojang_classic_cape": "Классический плащ Mojang", "item.sanctuary.blue_scroll_cape": "Плащ синего свитка", "item.sanctuary.white_cape": "Белый плащ", + "tooltip.sanctuary.chicken_armor": "Также надевается на взрослую курицу и защищает её.", "dimension.sanctuary.alpha": "Остров Альфа / Indev", "dimension.sanctuary.backroom": "Бэкрум", "message.sanctuary.backroom.unavailable": "Бэкрум недоступен в этом мире.", diff --git a/sanctuary/src/main/resources/sanctuary.mixins.json b/sanctuary/src/main/resources/sanctuary.mixins.json index 195795a..29fd3f3 100644 --- a/sanctuary/src/main/resources/sanctuary.mixins.json +++ b/sanctuary/src/main/resources/sanctuary.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_25", "mixins": [ "AbstractContainerMenuMixin", + "AnimalChickenArmorMixin", "BedBlockMixin", "ChestBlockEntityMixin", "ConsumableMixin", @@ -15,6 +16,7 @@ "ItemEntityMixin", "ItemTooltipMixin", "LivingEntityCompanionMixin", + "LivingEntityChickenArmorMixin", "PlayerOverflowMixin", "SlotMixin", "ServerGamePacketListenerMixin", @@ -28,6 +30,8 @@ "client.AbstractContainerScreenMixin", "client.AbstractRecipeBookScreenMixin", "client.CameraMixin", + "client.ChickenRendererMixin", + "client.ChickenRenderStateMixin", "client.CreativeModeInventoryScreenMixin", "client.CreativeSlotWrapperAccessor", "client.CreateWorldScreenWorldTabMixin", diff --git a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmorSmoke.java b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmorSmoke.java new file mode 100644 index 0000000..f313fcc --- /dev/null +++ b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryChickenArmorSmoke.java @@ -0,0 +1,37 @@ +package fr.koka99cab.sanctuary26.sanctuary.gameplay; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.server.Bootstrap; +import net.minecraft.SharedConstants; +import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponentMap; + +/** Executable coverage for the bounded adult-chicken armor equip policy. */ +public final class SanctuaryChickenArmorSmoke { + private SanctuaryChickenArmorSmoke() { + } + + public static void main(String[] args) { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + ItemStack armor = stack(Items.WOLF_ARMOR); + require(SanctuaryChickenArmor.canEquip(ItemStack.EMPTY, armor, false), + "Adult chickens must accept unused armadillo-scute armor"); + require(!SanctuaryChickenArmor.canEquip(ItemStack.EMPTY, armor, true), + "Baby chickens must not accept body armor"); + require(!SanctuaryChickenArmor.canEquip(armor.copy(), armor, false), + "Equipping must not replace an existing chicken armor stack"); + require(!SanctuaryChickenArmor.canEquip(ItemStack.EMPTY, + stack(Items.IRON_CHESTPLATE), false), + "Chicken armor must stay limited to the armadillo-scute item"); + } + + private static ItemStack stack(net.minecraft.world.item.Item item) { + return new ItemStack(new Holder.Direct<>(item, DataComponentMap.EMPTY), 1); + } + + private static void require(boolean condition, String message) { + if (!condition) throw new IllegalStateException(message); + } +}