feat: ajouter une cape craftable décorée par bannière #126

Merged
koka merged 1 commits from feature/craftable-banner-cape into main 2026-08-31 11:44:50 +00:00
29 changed files with 785 additions and 242 deletions
+9
View File
@@ -6,6 +6,15 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
- Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46.
- Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées.
## `26.2.0-alpha.226`
Module modifié : **Sanctuary `0.0.0-alpha.130`**. Les neuf autres modules conservent leur version issue de l'alpha.225.
- ajoute `sanctuary:white_cape`, une cape blanche craftable avec six laines blanches disposées comme une porte, en deux colonnes de trois ;
- permet d'associer simplement la cape et n'importe quelle bannière dans l'établi afin d'en conserver la couleur de fond et jusqu'à six motifs ;
- affiche les motifs dans l'inventaire et sur la cape équipée, avec la physique native des capes, y compris en multijoueur ;
- conserve les sauvegardes Sanctuary en data v13 ; le payload d'apparence transporte désormais la couleur et les motifs bornés, donc le protocole Sanctuary passe de 23 à 24 et client comme serveur doivent être mis à jour, sans nouveau monde ni réinitialisation.
## `26.2.0-alpha.225`
Modules modifiés : **Another World `0.0.0-alpha.56`**, **It's Alive `0.0.0-alpha.38`**, **I Like To Move It `0.0.0-alpha.18`**, **Only Fun `0.0.0-alpha.27`** et **Sanctuary `0.0.0-alpha.129`**. Les cinq autres modules conservent leur version issue de l'alpha.224.
+3 -1
View File
@@ -22,7 +22,7 @@ Licence : **GNU GPL 3.0 or Later** (`GPL-3.0-or-later`)
Les descriptions FR/EN/RU sont déclarées dans chaque `fabric.mod.json` et dans les fichiers de langue. Les images fournies servent d'icônes Fabric de leur module.
Version courante du pack distribué : **`26.2.0-alpha.225`**. Cette release distribue Another World **`0.0.0-alpha.56`**, It's Alive **`0.0.0-alpha.38`**, I Like To Move It **`0.0.0-alpha.18`**, Only Fun **`0.0.0-alpha.27`** et Sanctuary **`0.0.0-alpha.129`** ; les cinq autres modules conservent leur version issue de l'alpha.224.
Version courante du pack distribué : **`26.2.0-alpha.226`**. Cette release distribue Sanctuary **`0.0.0-alpha.130`** ; les neuf autres modules conservent leur version issue de l'alpha.225.
Lalpha.225 corrige quatre tickets : posséder un coffre-fort devient obligatoire pour en braquer un autre, les familiers temporaires ne donnent plus de butin, dexpérience, de lootbox ni de progression de quête, les seize variantes Canaplia survivent à tout leur cycle de culture, et le Magic Carpet retrouve la texture historique du Totem de Notch à la place de la laine violette. Les IDs, sauvegardes, formats de données et protocoles restent compatibles.
@@ -116,6 +116,8 @@ Lalpha.173 centralise laudio du Miner : une seule boucle de frappe sur le
Lalpha.171 rassemble les **46 capes** dans un onglet créatif dédié « Capes ». Elles ne sont plus mélangées à longlet vanilla Tools & Utilities et ne sont donc affichées quune seule fois.
Lalpha.226 ajoute une 47e cape, `sanctuary:white_cape`, distincte de la collection Only Fun. Elle se fabrique avec six laines blanches en deux colonnes de trois, puis accepte une bannière dans une recette simple pour en conserver la couleur et jusqu’à six motifs ; les motifs saffichent dans linventaire comme une fois la cape équipée.
Lalpha.170 ajoute les pictogrammes fournis aux emplacements vides de la cape et du familier. Ils disparaissent dès quune cape ou un œuf est équipé, sans modifier les objets, la persistance ni le protocole.
Lalpha.169 agrandit la collection de 13 à **46 capes équipables**. Les 33 nouvelles textures 64×32 fournies sont conservées telles quelles, avec une icône dinventaire, un modèle et des traductions FR/EN/RU pour chaque cape. Elles utilisent le slot, la persistance et le rendu multijoueur déjà existants, restent sans recette et rejoignent toutes la lootbox de capes Only Fun. Les caméras Canon restent accessibles dans la lootbox normale et peuvent désormais aussi apparaître directement, très rarement, dans les coffres dexploration et du donjon de cobblestone.
+306 -189
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -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.129
sanctuary_version=0.0.0-alpha.130
sanctuary_lifecycle=active
pack_version=26.2.0-alpha.225
pack_version=26.2.0-alpha.226
maven_group=fr.koka99cab.sanctuary26
@@ -0,0 +1,49 @@
{
"schema_version": 1,
"status": "released",
"source": {
"pack_version": "26.2.0-alpha.225",
"modules": {
"sanctuary": "0.0.0-alpha.129"
}
},
"target": {
"pack_version": "26.2.0-alpha.226",
"modules": {
"sanctuary": "0.0.0-alpha.130"
}
},
"white_cape": {
"owner": "sanctuary",
"item_id": "sanctuary:white_cape",
"crafting_material": "minecraft:white_wool",
"crafting_material_count": 6,
"crafting_pattern": ["WW", "WW", "WW"],
"banner_decoration_recipe": "cape_plus_banner",
"accepted_banners": "#minecraft:banners",
"base_color_preserved": true,
"maximum_pattern_layers": 6,
"inventory_pattern_rendering": true,
"equipped_pattern_rendering": true,
"vanilla_cape_physics": true,
"included_in_onlyfun_lootbox": false
},
"compatibility": {
"existing_saves_supported": true,
"new_world_required": false,
"save_reset_required": false,
"registry_ids_added": ["sanctuary:white_cape"],
"persistent_data_formats_changed": false,
"sanctuary_data_version": 13,
"sanctuary_source_network_protocol": 23,
"sanctuary_target_network_protocol": 24,
"sanctuary_network_protocol_changed": true,
"client_server_update_required": true
},
"distribution": {
"pack_release_updated": true,
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true
}
}
+11 -11
View File
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
[[files]]
file = "mods/ambiance-0.0.0-alpha.22.jar"
hash = "d6c44f09ddc455ae0cec02a1568e07036f11a3b2d105659e9aa14289357ddb47"
hash = "0e862db1c5efb5c0745b82f991d5480f4305822c3d3f53ed38e2d2d47efabee1"
[[files]]
file = "mods/anotherworld-0.0.0-alpha.56.jar"
hash = "ede131b654d26110af8c7e64238c1b5f99634c3e1307346953ad6ab622ccfedc"
hash = "cec8ed493f3c86e4c4481081be110382407f7a5c631b7cf27dd80f80c6f2e92a"
[[files]]
file = "mods/events-0.0.0-alpha.0.jar"
hash = "9ccc30733117ab9984df3d69b98ab82d731578a6c35116595c96f2362ea22b76"
hash = "16bf9fc194988086f51ba6987546b6aa5bc634fe8fcb118b88540ed16991049c"
[[files]]
file = "mods/fabric-api.pw.toml"
@@ -31,7 +31,7 @@ metafile = true
[[files]]
file = "mods/iliketomoveit-0.0.0-alpha.18.jar"
hash = "f0ccb5a3f411cb01bcf328a139a1f90e8357587c549778f98354d910d69b2616"
hash = "94044341de08c9b89e0c1e6af6857da64b472fadbc94051ab76fc3cf1a0f0006"
[[files]]
file = "mods/iris.pw.toml"
@@ -40,7 +40,7 @@ metafile = true
[[files]]
file = "mods/itsalive-0.0.0-alpha.38.jar"
hash = "6dab7b2dca11cc1cd9789ed66cc8403b849cb73227d965d979eeebcf5c201309"
hash = "a42885703439646687ac281b1c124d1cddb3ca0595a41d930733b94bd8f1aa2f"
[[files]]
file = "mods/jei.pw.toml"
@@ -49,23 +49,23 @@ metafile = true
[[files]]
file = "mods/masterkey-0.0.0-alpha.9.jar"
hash = "ab0241386a0958505622487318c2e2e269e57e793ef873ed71dac8e0f420338b"
hash = "979c14e0a527be63688e97af8349be07b027d3149107c3e9e728a2b1e1ec65ff"
[[files]]
file = "mods/onlyfun-0.0.0-alpha.27.jar"
hash = "15dbc6a9c3d4fef699c1f62f989c1d47e366cc781747c76640f5de668ea00510"
hash = "023ba80a8f3326a6b534b343753d5630d3be0700d25613ea21057cbfe44fdc73"
[[files]]
file = "mods/ouch-0.0.0-alpha.11.jar"
hash = "5bc1e244309a8835ef1d7869225201179cbee12853fb2f2390bfd47f2fcc3c4a"
hash = "2c15c9d0adeb315b2c3a6796f27c9dc62dd33365f35ea584552d97500b833eb8"
[[files]]
file = "mods/redstoner-0.0.0-alpha.13.jar"
hash = "d913aac62b6e7152c5751ea0ddc820a98a0b74c3864f4c0bdbfc3b44eeaa3417"
hash = "753835558c1037e39ca669737190013bd59a3ab16f7d5156c52e940c54ff9c60"
[[files]]
file = "mods/sanctuary-0.0.0-alpha.129.jar"
hash = "0b67c37680e5004b7f08ac9de32b501fc8f2025fe5a7649858948f14a5d8f5f5"
file = "mods/sanctuary-0.0.0-alpha.130.jar"
hash = "8906be26de369903a0d5152d80a95a1c47b3468e4afca17ab4683b8300ad69a9"
[[files]]
file = "mods/simple-voice-chat.pw.toml"
+2 -2
View File
@@ -1,12 +1,12 @@
name = "Sanctuary 26.2"
author = "KOKA99CAB"
version = "26.2.0-alpha.225"
version = "26.2.0-alpha.226"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "19d66dcd7dbbc8e1a9471dcb259a9c9aad727acbc8428f114a48c05c2fd1d8de"
hash = "d3b929a596081adc3960866a2f939d4155d1b67ecb22bfe6050931a7d1d0f0a2"
[versions]
fabric = "0.19.3"
+1 -1
View File
@@ -3,7 +3,7 @@
"game": "minecraft",
"name": "Sanctuary 26.2",
"summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.",
"versionId": "26.2.0-alpha.225",
"versionId": "26.2.0-alpha.226",
"files": [
{
"path": "mods/fabric-api-0.158.0+26.2.jar",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.225",
"pack_version": "26.2.0-alpha.226",
"release_channel": "alpha",
"minecraft_version": "26.2",
"fabric_loader_version": "0.19.3",
@@ -9,7 +9,7 @@
"project_id": "FgOqyAXd",
"project_slug": "sanctuary-koka99",
"project_url": "https://modrinth.com/modpack/sanctuary-koka99",
"primary_file": "sanctuary-26.2.0-alpha.225.mrpack",
"primary_file": "sanctuary-26.2.0-alpha.226.mrpack",
"shared_client_server_pack": true,
"update_owner": "packwiz-installer",
"check_on_launch": true,
+88 -17
View File
@@ -116,6 +116,14 @@ tasks.register("capeJoinSyncSmoke", JavaExec) {
mainClass = "fr.koka99cab.sanctuary26.sanctuary.gameplay.CapeJoinSyncQueueSmoke"
}
tasks.register("capeSyncPayloadSmoke", JavaExec) {
group = "verification"
description = "Checks banner cape colors and the six-pattern network bound."
dependsOn tasks.named("testClasses")
classpath = sourceSets.test.runtimeClasspath
mainClass = "fr.koka99cab.sanctuary26.sanctuary.network.CapeSyncPayloadSmoke"
}
tasks.register("inventorySortSmoke", JavaExec) {
group = "verification"
description = "Checks that storage stacks merge and sort while the hotbar row stays out of the sort."
@@ -183,7 +191,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| !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")
@@ -197,7 +205,7 @@ tasks.register("verifyBlackMarket") {
|| !minecraftMixin.contains('method = "pickBlockOrEntity"')
|| !middleClickPayload.contains('SanctuaryMod.id("c2s/black_market_interact")')
|| !middleClickPayload.contains("No client position, listing ID, item or price is trusted")
|| !network.contains("PROTOCOL_VERSION = 23")
|| !network.contains("PROTOCOL_VERSION = 24")
|| !network.contains("BlackMarketService.middleClick(context.player())")
|| !service.contains("loadedDeliveryBox") || !service.contains("PARCEL_RETENTION_MILLIS")
|| !policy.contains("DataComponents.CONTAINER") || !policy.contains("DataComponents.BUNDLE_CONTENTS")
@@ -1193,12 +1201,12 @@ tasks.register("verifySanctuary") {
def manifest = new JsonSlurper().parse(file("src/main/resources/fabric.mod.json"))
if (manifest.custom?.sanctuary26?.data_version != 13
|| manifest.custom?.sanctuary26?.network_protocol != 23
|| manifest.custom?.sanctuary26?.network_protocol != 24
|| manifest.depends?.jei != ">=30.24.0.176"
|| manifest.depends?."fabric-api" != ">=0.155.0+26.2"
|| !manifest.entrypoints?.client?.contains("fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryClient")
|| !manifest.mixins?.contains("sanctuary.mixins.json")) {
throw new GradleException("Sanctuary must expose data v13, protocol 22, JEI and the client entrypoint")
throw new GradleException("Sanctuary must expose data v13, protocol 24, JEI and the client entrypoint")
}
def capeMigration = new JsonSlurper().parse(rootProject.file(
"pack/migrations/26.2.0-alpha.155-cape-equipment-to-alpha.156.json"))
@@ -2899,7 +2907,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| 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
@@ -3065,7 +3073,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| 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
@@ -3103,7 +3111,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| 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]
@@ -3134,7 +3142,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| migration.target?.pack_version != "26.2.0-alpha.195"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.111"
|| migration.compatibility?.sanctuary_data_version != 11
@@ -3165,7 +3173,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.112"
|| migration.mailbox?.id != "sanctuary:delivery_box"
|| migration.mailbox?.horizontal_facing != true
@@ -3193,7 +3201,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| !transfer.contains("shouldAppendOverflow")
|| !transfer.contains("isVanillaStorageRange")
|| !transfer.contains("hotbarStart")
@@ -3239,7 +3247,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.113"
|| migration.quest_requests?.save_key != "quest_requests"
|| migration.quest_requests?.data_version != 1
@@ -3251,7 +3259,7 @@ tasks.register("verifyQuestRequests") {
|| !data.contains('SAVE_KEY = "quest_requests"') || !data.contains("DATA_VERSION = 3")
|| !service.contains("PlayerBlockBreakEvents.BEFORE")
|| !service.contains("player.blockPosition().distSqr(payload.pos()) > 64.0D")
|| !network.contains("PROTOCOL_VERSION = 23")
|| !network.contains("PROTOCOL_VERSION = 24")
|| !network.contains("QuestRequestSubmitPayload.ID")
|| !network.contains("QuestRequestAcceptPayload.ID")
|| !registry.contains('SanctuaryMod.id("quest_request_sign")')
@@ -3297,7 +3305,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| 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"
@@ -3317,7 +3325,7 @@ tasks.register("verifyQuestRequestContracts") {
|| !service.contains('REWARD_LOAN = "loan"')
|| !service.contains('PLAYER.equals(targetKind)')
|| !screen.contains("rewardQuantity") || !screen.contains("killTargetIndex")
|| !network.contains("PROTOCOL_VERSION = 23")
|| !network.contains("PROTOCOL_VERSION = 24")
|| standingModel.textures?.all != "sanctuary:block/pink_quest_request_sign"
|| itemModel.textures?.layer0 != "sanctuary:item/pink_quest_request_sign"
|| blockTexture == null || blockTexture.width != 32 || blockTexture.height != 32
@@ -3366,7 +3374,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| 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"
@@ -3387,7 +3395,7 @@ tasks.register("verifyQuestRequestEscrow") {
|| !service.contains('"quest_request_reward"')
|| !service.contains('"quest_request_delivery"')
|| service.contains("sanctuary_quest_accept")
|| !network.contains("PROTOCOL_VERSION = 23")
|| !network.contains("PROTOCOL_VERSION = 24")
|| !network.contains("QuestRequestAcceptPayload.ID")
|| !network.contains("QuestRequestConfirmPayload.ID")
|| !acceptPayload.contains("readUtf(36)")
@@ -3411,6 +3419,68 @@ tasks.named("verifySanctuary") {
dependsOn tasks.named("verifyInventoryShiftClick")
}
tasks.register("verifyCraftableBannerCape") {
group = "verification"
description = "Checks the white wool recipe, banner decoration and equipped pattern rendering."
inputs.file(rootProject.file(
"pack/migrations/26.2.0-alpha.225-craftable-banner-cape-to-alpha.226.json"))
inputs.file(file("src/main/resources/data/sanctuary/recipe/white_cape.json"))
inputs.file(file("src/main/resources/data/sanctuary/recipe/white_cape_banner.json"))
inputs.file(file("src/main/resources/assets/sanctuary/items/white_cape.json"))
inputs.files(fileTree("src/main/resources/assets/sanctuary/lang") { include "*.json" })
inputs.files(
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryCapes.java"),
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/CapeSyncPayload.java"),
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java"),
file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java"))
doLast {
def migration = new JsonSlurper().parse(rootProject.file(
"pack/migrations/26.2.0-alpha.225-craftable-banner-cape-to-alpha.226.json"))
def baseRecipe = new JsonSlurper().parse(file(
"src/main/resources/data/sanctuary/recipe/white_cape.json"))
def bannerRecipe = new JsonSlurper().parse(file(
"src/main/resources/data/sanctuary/recipe/white_cape_banner.json"))
def itemDefinition = new JsonSlurper().parse(file(
"src/main/resources/assets/sanctuary/items/white_cape.json"))
def capes = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryCapes.java").text
def payload = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/CapeSyncPayload.java").text
def networking = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java").text
def renderer = file(
"src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryBannerCapeLayer.java").text
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.130"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.130"
|| migration.white_cape?.crafting_material_count != 6
|| migration.white_cape?.maximum_pattern_layers != 6
|| baseRecipe.pattern != ["WW", "WW", "WW"]
|| baseRecipe.key?.W != "minecraft:white_wool"
|| baseRecipe.result?.id != "sanctuary:white_cape"
|| bannerRecipe.type != "minecraft:crafting_special_shielddecoration"
|| bannerRecipe.banner != "#minecraft:banners"
|| bannerRecipe.target != "sanctuary:white_cape"
|| itemDefinition.model?.type != "minecraft:special"
|| itemDefinition.model?.model?.type != "minecraft:banner"
|| locales.any { !it.containsKey("item.sanctuary.white_cape") }
|| !capes.contains('WHITE_CAPE_PATH = "white_cape"')
|| !payload.contains("MAX_PATTERN_LAYERS = 6")
|| !payload.contains("DataComponents.BASE_COLOR")
|| !payload.contains("DataComponents.BANNER_PATTERNS")
|| !networking.contains("PROTOCOL_VERSION = 24")
|| !renderer.contains("BannerRenderer.submitPatterns")) {
throw new GradleException("Sanctuary alpha.130 craftable banner cape is incomplete")
}
}
}
tasks.named("verifySanctuary") {
dependsOn tasks.named("verifyCraftableBannerCape")
}
tasks.register("verifyAlphaIndev") {
group = "verification"
description = "Checks the alpha.122 finite Indev world, migration and attribution contract."
@@ -3438,7 +3508,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.129"
if (project.version.toString() != "0.0.0-alpha.130"
|| migration.target?.modules?.sanctuary != "0.0.0-alpha.122"
|| migration.alpha_world?.dimension != "sanctuary:alpha"
|| migration.alpha_world?.legacy_dimension != "portals:alpha"
@@ -3475,6 +3545,7 @@ tasks.named("check") {
dependsOn tasks.named("questIdSuggestionsSmoke")
dependsOn tasks.named("alphaIndevGeneratorSmoke")
dependsOn tasks.named("capeJoinSyncSmoke")
dependsOn tasks.named("capeSyncPayloadSmoke")
dependsOn tasks.named("inventorySortSmoke")
dependsOn tasks.named("entityPolicySmoke")
dependsOn tasks.named("verifyAlphaIndev")
@@ -0,0 +1,98 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
import com.mojang.blaze3d.vertex.PoseStack;
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
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;
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.model.geom.builders.PartDefinition;
import net.minecraft.client.model.player.PlayerCapeModel;
import net.minecraft.client.model.player.PlayerModel;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.blockentity.BannerRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
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.component.DataComponents;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.item.ItemStack;
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<AvatarRenderState, PlayerModel> {
private static final ModelLayerLocation MODEL_LAYER =
new ModelLayerLocation(SanctuaryMod.id("banner_cape"), "main");
private final PlayerCapeModel model;
private final EquipmentAssetManager equipmentAssets;
private final EntityRendererProvider.Context context;
private SanctuaryBannerCapeLayer(RenderLayerParent<AvatarRenderState, PlayerModel> parent,
EntityRendererProvider.Context context) {
super(parent);
this.context = context;
this.model = new PlayerCapeModel(context.bakeLayer(MODEL_LAYER));
this.equipmentAssets = context.getEquipmentAssets();
}
public static void initialize() {
ModelLayerRegistry.registerModelLayer(MODEL_LAYER, SanctuaryBannerCapeLayer::createLayer);
LivingEntityRenderLayerRegistrationCallback.EVENT.register((entityType, renderer, helper, context) -> {
if (entityType == EntityTypes.PLAYER && renderer instanceof AvatarRenderer<?> avatarRenderer) {
register(helper, avatarRenderer, context);
}
});
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static void register(LivingEntityRenderLayerRegistrationCallback.RegistrationHelper helper,
AvatarRenderer<?> renderer, EntityRendererProvider.Context context) {
RenderLayerParent<AvatarRenderState, PlayerModel> parent =
(RenderLayerParent<AvatarRenderState, PlayerModel>) (RenderLayerParent) renderer;
helper.register(new SanctuaryBannerCapeLayer(parent, context));
}
private static LayerDefinition createLayer() {
MeshDefinition mesh = PlayerModel.createMesh(CubeDeformation.NONE, false);
PartDefinition root = mesh.getRoot().clearRecursively();
PartDefinition body = root.getChild("body");
body.addOrReplaceChild("cape", CubeListBuilder.create().texOffs(0, 0)
.addBox(-5.0F, 0.0F, -1.0F, 10.0F, 16.0F, 1.0F,
CubeDeformation.NONE, 2.0F, 2.5F),
PartPose.offsetAndRotation(0.0F, 0.0F, 2.0F, 0.0F, (float) Math.PI, 0.0F));
return LayerDefinition.create(mesh, 64, 64);
}
@Override
public void submit(PoseStack poseStack, SubmitNodeCollector collector, int packedLight,
AvatarRenderState state, float yRot, float xRot) {
SanctuaryClientCapes.Appearance appearance =
((SanctuaryCapeRenderState) state).sanctuary$capeAppearance();
if (state.isInvisible || appearance == null || !appearance.bannerCape()
|| hasLayer(state.chestEquipment, EquipmentClientInfo.LayerType.WINGS)) return;
poseStack.pushPose();
if (hasLayer(state.chestEquipment, EquipmentClientInfo.LayerType.HUMANOID)) {
poseStack.translate(0.0F, -0.053125F, 0.06875F);
}
BannerRenderer.submitPatterns(context.getSprites(), poseStack, collector, packedLight,
OverlayTexture.NO_OVERLAY, model, state, true, appearance.baseColor(),
appearance.patterns(), null);
poseStack.popPose();
}
private boolean hasLayer(ItemStack stack, EquipmentClientInfo.LayerType layerType) {
Equippable equippable = stack.get(DataComponents.EQUIPPABLE);
return equippable != null && equippable.assetId().isPresent()
&& !equipmentAssets.get(equippable.assetId().get()).getLayers(layerType).isEmpty();
}
}
@@ -0,0 +1,8 @@
package fr.koka99cab.sanctuary26.sanctuary.client;
/** Mixin-backed bridge carrying equipped cape data into the extracted render state. */
public interface SanctuaryCapeRenderState {
SanctuaryClientCapes.Appearance sanctuary$capeAppearance();
void sanctuary$setCapeAppearance(SanctuaryClientCapes.Appearance appearance);
}
@@ -119,6 +119,7 @@ public final class SanctuaryClient implements ClientModInitializer {
SanctuaryBulkActionsClient.initialize();
SanctuaryHealthPreview.register();
CelestialSpyglassSelection.register();
SanctuaryBannerCapeLayer.initialize();
EntityRendererRegistry.register(SanctuaryRegistries.SEAT, NoopRenderer::new);
ClientTickEvents.END_CLIENT_TICK.register(SanctuaryClient::tick);
ClientPlayNetworking.registerGlobalReceiver(ProgressionSnapshotPayload.ID, (payload, context) ->
@@ -6,25 +6,33 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
/** Client mirror of server-authoritative equipped cape choices. */
public final class SanctuaryClientCapes {
private static final Map<UUID, Identifier> TEXTURES = new HashMap<>();
private static final Map<UUID, Appearance> APPEARANCES = new HashMap<>();
private SanctuaryClientCapes() {
}
public static void update(CapeSyncPayload payload) {
Identifier texture = SanctuaryCapes.texture(payload.capeItemId());
if (texture == null) TEXTURES.remove(payload.playerId());
else TEXTURES.put(payload.playerId(), texture);
boolean bannerCape = SanctuaryCapes.isBannerCape(payload.capeItemId());
if (texture == null && !bannerCape) APPEARANCES.remove(payload.playerId());
else APPEARANCES.put(payload.playerId(), new Appearance(
texture, bannerCape, payload.baseColor(), payload.patterns()));
}
public static Identifier texture(UUID playerId) {
return TEXTURES.get(playerId);
public static Appearance appearance(UUID playerId) {
return APPEARANCES.get(playerId);
}
public static void clear() {
TEXTURES.clear();
APPEARANCES.clear();
}
public record Appearance(Identifier texture, boolean bannerCape, DyeColor baseColor,
BannerPatternLayers patterns) {
}
}
@@ -62,7 +62,7 @@ public final class SanctuaryCapeService {
private static void send(ServerPlayer receiver, ServerPlayer owner) {
if (!ServerPlayNetworking.canSend(receiver, CapeSyncPayload.ID)) return;
String capeId = SanctuaryCapes.itemId(SanctuaryInventoryCapacity.equippedCape(owner));
ServerPlayNetworking.send(receiver, new CapeSyncPayload(owner.getUUID(), capeId));
ServerPlayNetworking.send(receiver, CapeSyncPayload.fromStack(
owner.getUUID(), SanctuaryInventoryCapacity.equippedCape(owner)));
}
}
@@ -14,8 +14,10 @@ import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
/** Registration and immutable visual definitions for Sanctuary's collectible capes. */
/** Registration and immutable visual definitions for Sanctuary's equippable capes. */
public final class SanctuaryCapes {
public static final String WHITE_CAPE_PATH = "white_cape";
public static final String WHITE_CAPE_ID = "sanctuary:" + WHITE_CAPE_PATH;
public static final List<String> PATHS = List.of(
"transgender_cape", "mojang_cape", "founders_cape", "nes_cape", "pride_cape",
"pink_cape", "feesh_gang_cape", "gameboy_cape", "blue_cape", "chicken_cape",
@@ -28,7 +30,7 @@ public final class SanctuaryCapes {
"vanilla_cape", "java_edition_cape", "migration_cape", "creeper_anniversary_cape",
"java_twitch_cape", "minecon_enderman_cape", "iron_golem_minecon_cape", "minecon_2013_cape",
"crafter_twitchcon_cape", "minecon_pickaxe_cape", "minecon_2011_cape", "mojang_classic_cape",
"blue_scroll_cape"
"blue_scroll_cape", WHITE_CAPE_PATH
);
private static final Map<Identifier, Definition> DEFINITIONS = registerDefinitions();
private static final ResourceKey<CreativeModeTab> CREATIVE_TAB_KEY = ResourceKey.create(
@@ -74,6 +76,10 @@ public final class SanctuaryCapes {
return definition == null ? null : definition.texture();
}
public static boolean isBannerCape(String itemId) {
return WHITE_CAPE_ID.equals(itemId);
}
public static List<Definition> definitions() {
return List.copyOf(DEFINITIONS.values());
}
@@ -85,8 +91,9 @@ public final class SanctuaryCapes {
ResourceKey<Item> key = ResourceKey.create(Registries.ITEM, id);
Item item = Registry.register(BuiltInRegistries.ITEM, key,
new SanctuaryCapeItem(new Item.Properties().stacksTo(1).setId(key)));
definitions.put(id, new Definition(id, item,
SanctuaryMod.id("entity/cape/" + path)));
Identifier texture = WHITE_CAPE_PATH.equals(path)
? null : SanctuaryMod.id("entity/cape/" + path);
definitions.put(id, new Definition(id, item, texture));
}
return Map.copyOf(definitions);
}
@@ -0,0 +1,23 @@
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryCapeRenderState;
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryClientCapes;
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
/** Stores the server-authoritative dynamic cape appearance on each extracted player state. */
@Mixin(AvatarRenderState.class)
abstract class AvatarRenderStateMixin implements SanctuaryCapeRenderState {
@Unique private SanctuaryClientCapes.Appearance sanctuary$capeAppearance;
@Override
public SanctuaryClientCapes.Appearance sanctuary$capeAppearance() {
return sanctuary$capeAppearance;
}
@Override
public void sanctuary$setCapeAppearance(SanctuaryClientCapes.Appearance appearance) {
sanctuary$capeAppearance = appearance;
}
}
@@ -1,6 +1,7 @@
package fr.koka99cab.sanctuary26.sanctuary.mixin.client;
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryClientCapes;
import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryCapeRenderState;
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
import net.minecraft.core.ClientAsset;
@@ -21,7 +22,13 @@ abstract class AvatarRendererMixin {
)
private void sanctuary$applyEquippedCape(Avatar avatar, AvatarRenderState state,
float partialTick, CallbackInfo ci) {
Identifier texture = SanctuaryClientCapes.texture(avatar.getUUID());
SanctuaryClientCapes.Appearance appearance = SanctuaryClientCapes.appearance(avatar.getUUID());
((SanctuaryCapeRenderState) state).sanctuary$setCapeAppearance(appearance);
if (appearance == null || appearance.bannerCape()) {
if (appearance != null) state.showCape = false;
return;
}
Identifier texture = appearance.texture();
PlayerSkin skin = state.skin;
if (texture == null || skin == null) return;
state.skin = PlayerSkin.insecure(skin.body(), new ClientAsset.ResourceTexture(texture),
@@ -1,13 +1,22 @@
package fr.koka99cab.sanctuary26.sanctuary.network;
import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod;
import fr.koka99cab.sanctuary26.sanctuary.gameplay.SanctuaryCapes;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import net.minecraft.core.component.DataComponents;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
/** Announces one player's currently equipped Sanctuary cape to clients. */
public record CapeSyncPayload(UUID playerId, String capeItemId) implements CustomPacketPayload {
public record CapeSyncPayload(UUID playerId, String capeItemId, DyeColor baseColor,
BannerPatternLayers patterns) implements CustomPacketPayload {
public static final int MAX_PATTERN_LAYERS = 6;
public static final Type<CapeSyncPayload> ID = new Type<>(SanctuaryMod.id("s2c/cape_sync"));
public static final StreamCodec<RegistryFriendlyByteBuf, CapeSyncPayload> CODEC =
CustomPacketPayload.codec(CapeSyncPayload::write, CapeSyncPayload::new);
@@ -15,15 +24,47 @@ public record CapeSyncPayload(UUID playerId, String capeItemId) implements Custo
public CapeSyncPayload {
if (playerId == null) throw new IllegalArgumentException("Cape player UUID cannot be null");
capeItemId = capeItemId == null ? "" : capeItemId;
baseColor = baseColor == null ? DyeColor.WHITE : baseColor;
patterns = bounded(patterns);
}
private CapeSyncPayload(RegistryFriendlyByteBuf buffer) {
this(buffer.readUUID(), buffer.readUtf(128));
this(buffer.readUUID(), buffer.readUtf(128), DyeColor.STREAM_CODEC.decode(buffer), readPatterns(buffer));
}
private void write(RegistryFriendlyByteBuf buffer) {
buffer.writeUUID(playerId);
buffer.writeUtf(capeItemId, 128);
DyeColor.STREAM_CODEC.encode(buffer, baseColor);
buffer.writeVarInt(patterns.layers().size());
patterns.layers().forEach(layer -> BannerPatternLayers.Layer.STREAM_CODEC.encode(buffer, layer));
}
public static CapeSyncPayload fromStack(UUID playerId, ItemStack stack) {
String capeItemId = SanctuaryCapes.itemId(stack);
DyeColor baseColor = stack.get(DataComponents.BASE_COLOR);
BannerPatternLayers patterns = stack.get(DataComponents.BANNER_PATTERNS);
return new CapeSyncPayload(playerId, capeItemId, baseColor, patterns);
}
private static BannerPatternLayers readPatterns(RegistryFriendlyByteBuf buffer) {
int size = buffer.readVarInt();
if (size < 0 || size > MAX_PATTERN_LAYERS) {
throw new IllegalArgumentException("Cape banner pattern count must be between 0 and "
+ MAX_PATTERN_LAYERS + ": " + size);
}
List<BannerPatternLayers.Layer> layers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
layers.add(BannerPatternLayers.Layer.STREAM_CODEC.decode(buffer));
}
return layers.isEmpty() ? BannerPatternLayers.EMPTY : new BannerPatternLayers(List.copyOf(layers));
}
private static BannerPatternLayers bounded(BannerPatternLayers patterns) {
if (patterns == null || patterns.layers().isEmpty()) return BannerPatternLayers.EMPTY;
List<BannerPatternLayers.Layer> layers = patterns.layers();
if (layers.size() <= MAX_PATTERN_LAYERS) return patterns;
return new BannerPatternLayers(List.copyOf(layers.subList(0, MAX_PATTERN_LAYERS)));
}
@Override public Type<? extends CustomPacketPayload> type() { return ID; }
@@ -24,7 +24,7 @@ import net.minecraft.server.level.ServerPlayer;
* Networking contract for Sanctuary. Payload IDs must use the module namespace.
*/
public final class SanctuaryNetworking {
public static final int PROTOCOL_VERSION = 23;
public static final int PROTOCOL_VERSION = 24;
private static boolean initialized;
private SanctuaryNetworking() {
@@ -0,0 +1,34 @@
{
"model": {
"type": "minecraft:special",
"base": "minecraft:item/template_banner",
"model": {
"type": "minecraft:banner",
"color": "white"
},
"transformation": {
"left_rotation": [
0.0,
0.0,
0.0,
1.0
],
"right_rotation": [
0.0,
0.0,
0.0,
1.0
],
"scale": [
0.6666667,
-0.6666667,
-0.6666667
],
"translation": [
0.5,
0.0,
0.5
]
}
}
}
@@ -514,6 +514,7 @@
"item.sanctuary.minecon_2011_cape": "Minecon 2011 Cape",
"item.sanctuary.mojang_classic_cape": "Mojang Classic Cape",
"item.sanctuary.blue_scroll_cape": "Blue Scroll Cape",
"item.sanctuary.white_cape": "White Cape",
"dimension.sanctuary.alpha": "Alpha / Indev Island",
"screen.sanctuary.atlas.landmark.boss": "Defeated boss",
"message.sanctuary.notch_defeated": "Notch Titan has been defeated. The victory is now recorded in this world."
@@ -514,6 +514,7 @@
"item.sanctuary.minecon_2011_cape": "Cape Minecon 2011",
"item.sanctuary.mojang_classic_cape": "Cape Mojang classique",
"item.sanctuary.blue_scroll_cape": "Cape parchemin bleu",
"item.sanctuary.white_cape": "Cape blanche",
"dimension.sanctuary.alpha": "Île Alpha / Indev",
"screen.sanctuary.atlas.landmark.boss": "Boss vaincu",
"message.sanctuary.notch_defeated": "Notch Titan a été vaincu. La victoire est désormais inscrite dans ce monde."
@@ -470,6 +470,7 @@
"item.sanctuary.minecon_2011_cape": "Плащ Minecon 2011",
"item.sanctuary.mojang_classic_cape": "Классический плащ Mojang",
"item.sanctuary.blue_scroll_cape": "Плащ синего свитка",
"item.sanctuary.white_cape": "Белый плащ",
"dimension.sanctuary.alpha": "Остров Альфа / Indev",
"screen.sanctuary.atlas.landmark.boss": "Побеждённый босс",
"message.sanctuary.notch_defeated": "Нотч-Титан побеждён. Победа навсегда записана в этом мире."
@@ -0,0 +1,16 @@
{
"type": "minecraft:crafting_shaped",
"category": "equipment",
"key": {
"W": "minecraft:white_wool"
},
"pattern": [
"WW",
"WW",
"WW"
],
"result": {
"count": 1,
"id": "sanctuary:white_cape"
}
}
@@ -0,0 +1,8 @@
{
"type": "minecraft:crafting_special_shielddecoration",
"banner": "#minecraft:banners",
"result": {
"id": "sanctuary:white_cape"
},
"target": "sanctuary:white_cape"
}
+1 -1
View File
@@ -33,7 +33,7 @@
"system_type": "gameplay-system",
"namespace": "sanctuary",
"data_version": 13,
"network_protocol": 23,
"network_protocol": 24,
"lifecycle": "${lifecycle}",
"pack_version": "${pack_version}",
"descriptions": {
@@ -18,6 +18,7 @@
],
"client": [
"client.AbstractButtonMixin",
"client.AvatarRenderStateMixin",
"client.AvatarRendererMixin",
"client.AbstractContainerScreenAccessor",
"client.AbstractContainerScreenMixin",
@@ -0,0 +1,40 @@
package fr.koka99cab.sanctuary26.sanctuary.network;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import net.minecraft.core.Holder;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.entity.BannerPattern;
import net.minecraft.world.level.block.entity.BannerPatternLayers;
/** Executable coverage for the bounded banner appearance carried by cape synchronization. */
public final class CapeSyncPayloadSmoke {
private CapeSyncPayloadSmoke() {
}
public static void main(String[] args) {
List<BannerPatternLayers.Layer> layers = new ArrayList<>();
Holder<BannerPattern> pattern = Holder.direct(new BannerPattern(
Identifier.withDefaultNamespace("base"), "block.minecraft.banner.base.white"));
for (int i = 0; i < 8; i++) {
layers.add(new BannerPatternLayers.Layer(pattern, DyeColor.BLUE));
}
CapeSyncPayload payload = new CapeSyncPayload(UUID.fromString(
"12345678-1234-5678-9abc-def012345678"), "sanctuary:white_cape", DyeColor.RED,
new BannerPatternLayers(List.copyOf(layers)));
require(payload.baseColor() == DyeColor.RED, "Banner base color was not preserved");
require(payload.patterns().layers().size() == CapeSyncPayload.MAX_PATTERN_LAYERS,
"Cape synchronization did not cap banner patterns at six");
CapeSyncPayload blank = new CapeSyncPayload(UUID.randomUUID(), "", null, null);
require(blank.baseColor() == DyeColor.WHITE, "Blank cape payload did not default to white");
require(blank.patterns().layers().isEmpty(), "Blank cape payload carried patterns");
}
private static void require(boolean condition, String message) {
if (!condition) throw new IllegalStateException(message);
}
}