Compare commits

...
Author SHA1 Message Date
koka a698140963 release: prepare Sanctuary 26.2.0-alpha.230
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-02 09:14:55 +02:00
koka 88ec91e9f4 Merge pull request 'feat(sanctuary): corriger les capes et armer les poules' (#147) from feature/final-sanctuary-tickets into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: koka/sanctuary#147
2026-09-02 06:57:22 +00:00
koka e1d48e9017 feat(sanctuary): resolve final cape and chicken tickets
Verification 26.2 / check (pull_request) Canceled after 0s
2026-09-02 08:52:53 +02:00
koka cce58c27c8 Merge pull request 'feat(ranking): ajouter les panneaux de trophées automatiques' (#144) from feature/automatic-trophy-panel into main
Verification 26.2 / check (push) Canceled after 0s
Reviewed-on: koka/sanctuary#144
2026-09-02 06:28:41 +00:00
26 changed files with 757 additions and 260 deletions
+5 -2
View File
@@ -6,9 +6,9 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve
- Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46.
- Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées.
## Non publié
## `26.2.0-alpha.230`
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`
+2 -4
View File
@@ -22,11 +22,9 @@ 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.229`**. Cette release distribue Red-Stoner **`0.0.0-alpha.16`** ; les neuf autres modules conservent leur version issue de l'alpha.228.
Version courante du pack distribué : **`26.2.0-alpha.230`**. Cette release distribue Sanctuary **`0.0.0-alpha.136`** et Red-Stoner **`0.0.0-alpha.17`** ; les huit autres modules conservent leur version issue de l'alpha.229.
Le prochain module Sanctuary **`0.0.0-alpha.134`** attribue les quêtes communautaires terminées à leur preneur et répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom. Chaque niveau garantit désormais un lit par cellule de 10 × 10 chunks dans les nouveaux chunks, sans modifier les sauvegardes, les données v14 ni le protocole réseau 24.
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.
L’alpha.230 attribue les quêtes communautaires terminées à leur preneur et répartit les lits de sortie rares sur chacun des cinq niveaux de la Backroom. Elle ajoute aussi les classements publics et le panneau mural `redstoner:trophy_panel`, corrige la projection des couleurs et motifs de la cape blanche avec les sprites de bouclier, et permet d’équiper une poule adulte avec l’armure de loup vanilla en écailles de tatou. Les sauvegardes restent compatibles ; Sanctuary passe aux données v15 et Red-Stoner aux données v4 sans changer leurs protocoles réseau 24 et 7.
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.
+301 -220
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.135
sanctuary_version=0.0.0-alpha.136
sanctuary_lifecycle=active
pack_version=26.2.0-alpha.229
pack_version=26.2.0-alpha.230
maven_group=fr.koka99cab.sanctuary26
@@ -0,0 +1,44 @@
{
"schema_version": 1,
"status": "released",
"source": {
"pack_version": "26.2.0-alpha.229",
"modules": {
"sanctuary": "0.0.0-alpha.132",
"redstoner": "0.0.0-alpha.16"
}
},
"target": {
"pack_version": "26.2.0-alpha.230",
"modules": {
"sanctuary": "0.0.0-alpha.136",
"redstoner": "0.0.0-alpha.17"
}
},
"sanctuary_polish": {
"quest_completion_attribution": true,
"backroom_exit_beds_on_every_level": true,
"persistent_public_rankings": true,
"redstoner_trophy_panel": true,
"white_cape_shield_pattern_projection": true,
"adult_chicken_wolf_armor": true
},
"compatibility": {
"existing_saves_supported": true,
"new_world_required": false,
"save_reset_required": false,
"persistent_data_formats_changed": true,
"network_protocols_changed": false,
"sanctuary_data_version": 15,
"sanctuary_network_protocol": 24,
"redstoner_data_version": 4,
"redstoner_network_protocol": 7,
"client_server_update_required": true
},
"distribution": {
"pack_release_updated": true,
"packwiz_index_refreshed": true,
"prism_modrinth_index_updated": true,
"shared_client_server_mrpack": true
}
}
+12 -12
View File
@@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0"
[[files]]
file = "mods/ambiance-0.0.0-alpha.22.jar"
hash = "b4ba14b9ada5b4c1d97b039ee632a158ae266eba2faefee690e710cc5f0f993e"
hash = "cdc60ed8f15ca910f41954d9acb6a74ac103e85c73f665fd1bc67be7eab1f15a"
[[files]]
file = "mods/anotherworld-0.0.0-alpha.57.jar"
hash = "4c8a3315ba9db6799fb0f046b45d702d56fba65173abccbd81ab995f56690407"
hash = "634a122b406bd7206fd19947687fd7cb37e4cc4098822eecf3f0b7d1fea79d90"
[[files]]
file = "mods/events-0.0.0-alpha.0.jar"
hash = "e7304da48379eb39737b126431316644250ecbc5b6ec8a439cc12e96a9477fa4"
hash = "09bd8dfccca004de46ad3237728ef532de50d04c31b88f1e33721204d3c70cd8"
[[files]]
file = "mods/fabric-api.pw.toml"
@@ -31,7 +31,7 @@ metafile = true
[[files]]
file = "mods/iliketomoveit-0.0.0-alpha.18.jar"
hash = "bd0467d186737b59996a8b1589a7c1837c990890f1b21154aaca614fb042c556"
hash = "cd026c4735e945ec6cc19261165033dc10bcbbba75af412f4145e54437a61854"
[[files]]
file = "mods/iris.pw.toml"
@@ -40,7 +40,7 @@ metafile = true
[[files]]
file = "mods/itsalive-0.0.0-alpha.38.jar"
hash = "dd1869166cfeeb36b19b7e243e7ece32c925d362aa80782076d493decc6e2871"
hash = "d905a05edbcb841b173ef5b0090fe7a117ec06e119fba7e184338fec7671fde6"
[[files]]
file = "mods/jei.pw.toml"
@@ -49,23 +49,23 @@ metafile = true
[[files]]
file = "mods/masterkey-0.0.0-alpha.9.jar"
hash = "46ef9947e82368fbe60d696174b3a3c99960be29432f3ccd7daf2714927b5c12"
hash = "cef973db96c7523fe828a2ca57ce1283ed1da3b1ae3c646a88ea601ecfe4e825"
[[files]]
file = "mods/onlyfun-0.0.0-alpha.27.jar"
hash = "d01271cbccef9c3c5f66e3d56748cd69d953f9eb53d21c8a1575523d40e77c56"
hash = "1ff5b8853c5a813bbb6c6cf5787c8311d8e72760ed0e9c2adb1da897241e4e1e"
[[files]]
file = "mods/ouch-0.0.0-alpha.11.jar"
hash = "ca612f8e660744af81d5a2612f78f8ad61dacf746a22474837e8b52a22bb3204"
hash = "fd609cfe5f96ae1d2b41b2da7faf077047514772b56bbe130251db3bf72c2ac8"
[[files]]
file = "mods/redstoner-0.0.0-alpha.16.jar"
hash = "e273042c0f7b12ed0a13da3f75f41903a741a29399da353ecd5144e67dcb680e"
file = "mods/redstoner-0.0.0-alpha.17.jar"
hash = "d7bef2bdf533d2c83f7b05549c01a77028dd5edc60418c7244447ca61160f216"
[[files]]
file = "mods/sanctuary-0.0.0-alpha.132.jar"
hash = "72a946358be6039c706cf52563ffe61ec70477ffb4e7db80e71116758f5bd519"
file = "mods/sanctuary-0.0.0-alpha.136.jar"
hash = "973c12a3f26eb516bc1a053ebd18dab3f845645807448ff5638a1e9b26ba28bd"
[[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.229"
version = "26.2.0-alpha.230"
pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "6a5b1811c2dd2b905a67cd817b8e377a8b68589431650775e1d8aabe04ff32e9"
hash = "a78df91699f1ffa661e1bf59101891a217bd74334ee338b96824f5a9b745e443"
[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.229",
"versionId": "26.2.0-alpha.230",
"files": [
{
"path": "mods/fabric-api-0.159.0+26.2.jar",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.229",
"pack_version": "26.2.0-alpha.230",
"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.229.mrpack",
"primary_file": "sanctuary-26.2.0-alpha.230.mrpack",
"shared_client_server_pack": true,
"update_owner": "packwiz-installer",
"check_on_launch": true,
+68 -13
View File
@@ -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")
@@ -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<AvatarRenderState, PlayerModel> {
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<AvatarRenderStat
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),
CubeDeformation.NONE, SHIELD_PATTERN_X_SCALE, SHIELD_PATTERN_Y_SCALE),
PartPose.offsetAndRotation(0.0F, 0.0F, 2.0F, 0.0F, (float) Math.PI, 0.0F));
return LayerDefinition.create(mesh, 64, 64);
}
@@ -85,7 +87,7 @@ public final class SanctuaryBannerCapeLayer extends RenderLayer<AvatarRenderStat
poseStack.translate(0.0F, -0.053125F, 0.06875F);
}
BannerRenderer.submitPatterns(context.getSprites(), poseStack, collector, packedLight,
OverlayTexture.NO_OVERLAY, model, state, true, appearance.baseColor(),
OverlayTexture.NO_OVERLAY, model, state, false, appearance.baseColor(),
appearance.patterns(), null);
poseStack.popPose();
}
@@ -0,0 +1,64 @@
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.animal.chicken.ChickenModel;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.ChickenRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.EquipmentLayerRenderer;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.entity.state.ChickenRenderState;
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 vanilla armadillo-scute equipment around an adult chicken's torso. */
public final class SanctuaryChickenArmorLayer extends RenderLayer<ChickenRenderState, ChickenModel> {
private static final ModelLayerLocation MODEL_LAYER =
new ModelLayerLocation(SanctuaryMod.id("chicken_armor"), "main");
private final SanctuaryChickenArmorModel model;
private final EquipmentLayerRenderer equipmentRenderer;
private SanctuaryChickenArmorLayer(RenderLayerParent<ChickenRenderState, ChickenModel> 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<ChickenRenderState, ChickenModel> parent =
(RenderLayerParent<ChickenRenderState, ChickenModel>) (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);
}
}
@@ -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<ChickenRenderState> {
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);
}
}
@@ -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);
}
@@ -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) ->
@@ -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);
}
}
@@ -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<InteractionResult> 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);
}
}
}
@@ -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));
}
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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.",
@@ -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.",
@@ -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": "Бэкрум недоступен в этом мире.",
@@ -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",
@@ -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);
}
}