diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e7c00..a5de14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,12 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve ## Non distribué -Module modifié : **It's Alive `0.0.0-alpha.36`**. Le pack distribué reste en `26.2.0-alpha.210` jusqu’à la prochaine release. +Modules modifiés : **It's Alive `0.0.0-alpha.36`** et **Sanctuary `0.0.0-alpha.119`**. Le pack distribué reste en `26.2.0-alpha.210` jusqu’à la prochaine release. - remplace les pages noires du Livre interdit par l’affichage clair du livre vanilla ; - conserve les seize entrées, les découvertes personnelles, le partage sur pupitre, les IDs, les données v3 et le protocole réseau 4. +- rétablit l'accès au Black Market par clic molette sur un panneau, avec une intention vide et une nouvelle validation complète de la cible côté serveur ; +- conserve les données Sanctuary v11 et porte son protocole réseau de 21 à 22, sans nouvel ID persistant. ## `26.2.0-alpha.210` diff --git a/build.gradle b/build.gradle index acd66f7..47fb807 100644 --- a/build.gradle +++ b/build.gradle @@ -3429,7 +3429,7 @@ tasks.register("verifyProgressivePauseMenuRelease") { "sanctuary/src/main/resources/assets/sanctuary/lang/${locale}.json"))] } if (rootProject.pack_version != "26.2.0-alpha.210" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || rootProject.onlyfun_version != "0.0.0-alpha.21" || 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"] @@ -4128,7 +4128,7 @@ tasks.register("verifyShopPricingLayoutRelease") { def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) if (rootProject.pack_version != "26.2.0-alpha.210" || rootProject.onlyfun_version != "0.0.0-alpha.21" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || 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" @@ -4222,7 +4222,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.210" || rootProject.onlyfun_version != "0.0.0-alpha.21" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || 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"] @@ -4314,7 +4314,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.210" || rootProject.itsalive_version != "0.0.0-alpha.36" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || 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"] @@ -4331,7 +4331,7 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") { || migration.distribution?.jei?.prism_source != "30.24.0.176" || migration.distribution?.jei?.target != "30.26.0.186" || migration.compatibility?.sanctuary_target_network_protocol != 19 - || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 21 + || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 || release.pack_version != "26.2.0-alpha.210" || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.210.mrpack" || prism.versionId != "26.2.0-alpha.210" @@ -4414,7 +4414,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.210" || rootProject.itsalive_version != "0.0.0-alpha.36" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || 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" @@ -4425,7 +4425,7 @@ tasks.register("verifyQuestRequestContractsRelease") { || migration.quest_request_contracts?.right_click_shows_details_before_claim != true || migration.quest_request_contracts?.chat_accept_revalidates_distance_owner_sign_and_claim != true || migration.compatibility?.sanctuary_target_network_protocol != 20 - || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 21 + || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 || release.pack_version != "26.2.0-alpha.210" || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.210.mrpack" || prism.versionId != "26.2.0-alpha.210" @@ -4534,7 +4534,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.210" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || 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" @@ -4547,7 +4547,7 @@ tasks.register("verifyQuestRequestEscrowRelease") { || migration.quest_request_escrow?.reward_delivery_to_claimant_mailbox != true || migration.compatibility?.sanctuary_target_network_protocol != 21 || sanctuaryManifest.custom?.sanctuary26?.data_version != 11 - || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 21 + || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 || release.pack_version != "26.2.0-alpha.210" || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.210.mrpack" || prism.versionId != "26.2.0-alpha.210" @@ -4591,7 +4591,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { || rootProject.anotherworld_version != "0.0.0-alpha.55" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.iliketomoveit_version != "0.0.0-alpha.15" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || migration.source?.pack_version != "26.2.0-alpha.201" || migration.source?.modules != [ itsalive: "0.0.0-alpha.30", @@ -4626,7 +4626,7 @@ tasks.register("verifyWaystoneShopCulinaryAlpha202Release") { || moveItManifest.custom?.sanctuary26?.data_version != 3 || moveItManifest.custom?.sanctuary26?.network_protocol != 5 || sanctuaryManifest.custom?.sanctuary26?.data_version != 11 - || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 21 + || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 22 || release.pack_version != "26.2.0-alpha.210" || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.210.mrpack" || prism.versionId != "26.2.0-alpha.210" @@ -4749,7 +4749,7 @@ tasks.register("verifySafeVaultFeature") { }.keySet() if (rootProject.pack_version != "26.2.0-alpha.210" || rootProject.anotherworld_version != "0.0.0-alpha.55" - || rootProject.sanctuary_version != "0.0.0-alpha.118" + || rootProject.sanctuary_version != "0.0.0-alpha.119" || migration.schema_version != 1 || migration.source?.pack_version != "26.2.0-alpha.201" || migration.target?.pack_version != "26.2.0-alpha.202" @@ -5597,7 +5597,7 @@ tasks.register("verifyAlpha210Release") { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } if (rootProject.pack_version != "26.2.0-alpha.210" - || rootProject.sanctuary_version != expectedTarget.sanctuary + || rootProject.sanctuary_version != "0.0.0-alpha.119" || rootProject.onlyfun_version != expectedTarget.onlyfun || rootProject.iliketomoveit_version != expectedTarget.iliketomoveit || rootProject.itsalive_version != "0.0.0-alpha.36" @@ -5635,7 +5635,7 @@ tasks.register("verifyAlpha210Release") { || migration.distribution?.jei?.modrinth_version_id != "qaCQ7LGO" || migration.distribution?.jei?.packwiz_and_prism_synchronized != true || manifests.sanctuary.custom?.sanctuary26?.data_version != 11 - || manifests.sanctuary.custom?.sanctuary26?.network_protocol != 21 + || manifests.sanctuary.custom?.sanctuary26?.network_protocol != 22 || manifests.onlyfun.custom?.sanctuary26?.data_version != 2 || manifests.onlyfun.custom?.sanctuary26?.network_protocol != 2 || manifests.iliketomoveit.custom?.sanctuary26?.data_version != 3 diff --git a/gradle.properties b/gradle.properties index ce57167..13a895c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,7 +28,7 @@ iliketomoveit_version=0.0.0-alpha.15 iliketomoveit_lifecycle=active onlyfun_version=0.0.0-alpha.21 onlyfun_lifecycle=active -sanctuary_version=0.0.0-alpha.118 +sanctuary_version=0.0.0-alpha.119 sanctuary_lifecycle=active pack_version=26.2.0-alpha.210 maven_group=fr.koka99cab.sanctuary26 diff --git a/sanctuary/BLACK_MARKET.md b/sanctuary/BLACK_MARKET.md index c64ddef..e131339 100644 --- a/sanctuary/BLACK_MARKET.md +++ b/sanctuary/BLACK_MARKET.md @@ -9,18 +9,23 @@ n'importe aucune donnée du Shop. ## Utilisation 1. Le vendeur pose une Mailbox dans sa dimension et reste dans un rayon de 256 blocs. -2. Il tient le stack à vendre, s'accroupit et fait clic droit sur un panneau libre. +2. Il tient le stack à vendre et fait un clic molette sur un panneau libre. L'ancien + raccourci accroupi + clic droit reste accepté pour les habitudes existantes. 3. L'éditeur du panneau choisit quantité, prix libre et devise (`ruby` ou `sapphire`). -4. Un clic droit du vendeur rouvre l'éditeur. Il peut modifier prix, devise et quantité. +4. Un clic molette du vendeur rouvre l'éditeur. Il peut modifier prix, devise et quantité. Pour augmenter la quantité, il doit tenir des objets aux composants strictement identiques. La diminution retourne par colis. Shift + clic droit annule l'annonce. -5. Un autre joueur fait clic droit pour acheter. Paiement, retrait de l'annonce, +5. Un autre joueur fait clic molette pour acheter. Paiement, retrait de l'annonce, séquestre et création des colis s'exécutent atomiquement sur le thread serveur. Le panneau affiche l'objet, le prix et le pseudonyme coloré du vendeur. Il est protégé : seuls le vendeur et un opérateur peuvent le casser. Il n'existe aucune limite arbitraire d'annonces et aucune expiration tant que le panneau existe. +Le clic molette remplace uniquement l'action vanilla « choisir le bloc » lorsque la cible +est un panneau. Le client n'envoie ni position, ni objet, ni prix : le serveur refait son +propre rayon, vérifie la portée et résout le panneau avant toute opération. + ## Économie et livraison - Mise en vente : 1 rubis versé à la trésorerie serveur. diff --git a/sanctuary/build.gradle b/sanctuary/build.gradle index 25b0fec..2c2c3be 100644 --- a/sanctuary/build.gradle +++ b/sanctuary/build.gradle @@ -92,7 +92,12 @@ tasks.register("verifyBlackMarket") { inputs.file(file("BLACK_MARKET.md")) inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketData.java")) inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketInteractionRules.java")) inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketItemPolicy.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryClient.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/MinecraftMixin.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/BlackMarketInteractPayload.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java")) inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java")) inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardData.java")) inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestBoardGenerator.java")) @@ -102,15 +107,36 @@ tasks.register("verifyBlackMarket") { doLast { def data = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketData.java").text def service = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java").text + def interactionRules = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketInteractionRules.java").text def policy = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketItemPolicy.java").text + def client = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/SanctuaryClient.java").text + def minecraftMixin = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/MinecraftMixin.java").text + def middleClickPayload = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/BlackMarketInteractPayload.java").text + def network = file( + "src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java").text def shop = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopService.java").text 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 (!data.contains('SAVE_KEY = "black_market"') || !data.contains("DATA_VERSION = 2") + if (project.version.toString() != "0.0.0-alpha.119" + || !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") || !service.contains("PlayerBlockBreakEvents.BEFORE") || !service.contains('"modify"') + || !service.contains("public static void middleClick(ServerPlayer player)") + || !service.contains("player.pick(") + || !service.contains("player.blockInteractionRange() + 1.0D") + || !interactionRules.contains("middleClick || secondaryUse") + || !client.contains("handleBlackMarketMiddleClick") + || !client.contains("ClientPlayNetworking.send(new BlackMarketInteractPayload())") + || !minecraftMixin.contains('method = "pickBlock"') + || !middleClickPayload.contains('SanctuaryMod.id("c2s/black_market_interact")') + || !middleClickPayload.contains("No client position, listing ID, item or price is trusted") + || !network.contains("PROTOCOL_VERSION = 22") + || !network.contains("BlackMarketService.middleClick(context.player())") || !service.contains("loadedDeliveryBox") || !service.contains("PARCEL_RETENTION_MILLIS") || !policy.contains("DataComponents.CONTAINER") || !policy.contains("DataComponents.BUNDLE_CONTENTS") || !shop.contains("public static final boolean OPEN = true") @@ -1086,12 +1112,12 @@ tasks.register("verifySanctuary") { def manifest = new JsonSlurper().parse(file("src/main/resources/fabric.mod.json")) if (manifest.custom?.sanctuary26?.data_version != 11 - || manifest.custom?.sanctuary26?.network_protocol != 21 + || manifest.custom?.sanctuary26?.network_protocol != 22 || 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 v11, protocol 21, JEI and the client entrypoint") + throw new GradleException("Sanctuary must expose data v11, protocol 22, 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")) @@ -2792,7 +2818,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || 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 @@ -2958,7 +2984,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || 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 @@ -2996,7 +3022,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || 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] @@ -3027,7 +3053,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || migration.target?.pack_version != "26.2.0-alpha.195" || migration.target?.modules?.sanctuary != "0.0.0-alpha.111" || migration.compatibility?.sanctuary_data_version != 11 @@ -3058,7 +3084,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || migration.target?.modules?.sanctuary != "0.0.0-alpha.112" || migration.mailbox?.id != "sanctuary:delivery_box" || migration.mailbox?.horizontal_facing != true @@ -3086,7 +3112,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || !transfer.contains("shouldAppendOverflow") || !transfer.contains("isVanillaStorageRange") || !transfer.contains("hotbarStart") @@ -3132,7 +3158,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || migration.target?.modules?.sanctuary != "0.0.0-alpha.113" || migration.quest_requests?.save_key != "quest_requests" || migration.quest_requests?.data_version != 1 @@ -3144,7 +3170,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 = 21") + || !network.contains("PROTOCOL_VERSION = 22") || !network.contains("QuestRequestSubmitPayload.ID") || !network.contains("QuestRequestAcceptPayload.ID") || !registry.contains('SanctuaryMod.id("quest_request_sign")') @@ -3190,7 +3216,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || 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" @@ -3210,7 +3236,7 @@ tasks.register("verifyQuestRequestContracts") { || !service.contains('REWARD_LOAN = "loan"') || !service.contains('PLAYER.equals(targetKind)') || !screen.contains("rewardQuantity") || !screen.contains("killTargetIndex") - || !network.contains("PROTOCOL_VERSION = 21") + || !network.contains("PROTOCOL_VERSION = 22") || 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 @@ -3259,7 +3285,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.118" + if (project.version.toString() != "0.0.0-alpha.119" || 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" @@ -3280,7 +3306,7 @@ tasks.register("verifyQuestRequestEscrow") { || !service.contains('"quest_request_reward"') || !service.contains('"quest_request_delivery"') || service.contains("sanctuary_quest_accept") - || !network.contains("PROTOCOL_VERSION = 21") + || !network.contains("PROTOCOL_VERSION = 22") || !network.contains("QuestRequestAcceptPayload.ID") || !network.contains("QuestRequestConfirmPayload.ID") || !acceptPayload.contains("readUtf(36)") 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 6a5806b..04b25f4 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 @@ -11,6 +11,7 @@ import fr.koka99cab.sanctuary26.sanctuary.network.CelestialSkySnapshotPayload; import fr.koka99cab.sanctuary26.sanctuary.network.ShopSnapshotPayload; import fr.koka99cab.sanctuary26.sanctuary.network.CapeSyncPayload; import fr.koka99cab.sanctuary26.sanctuary.network.BlackMarketEditorPayload; +import fr.koka99cab.sanctuary26.sanctuary.network.BlackMarketInteractPayload; import fr.koka99cab.sanctuary26.sanctuary.network.QuestRequestEditorPayload; import fr.koka99cab.sanctuary26.sanctuary.network.QuestRequestConfirmPayload; import fr.koka99cab.sanctuary26.sanctuary.progression.SanctuaryProgressionCosts; @@ -34,6 +35,8 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.SimpleContainer; import net.minecraft.world.inventory.ChestMenu; import net.minecraft.world.inventory.CraftingMenu; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.phys.BlockHitResult; import org.lwjgl.glfw.GLFW; public final class SanctuaryClient implements ClientModInitializer { @@ -202,6 +205,15 @@ public final class SanctuaryClient implements ClientModInitializer { return switchHotbarRowKey != null && switchHotbarRowKey.matches(event); } + public static boolean handleBlackMarketMiddleClick(Minecraft client) { + if (client.player == null || client.level == null || client.gui.screen() != null + || !(client.hitResult instanceof BlockHitResult hit) + || !(client.level.getBlockEntity(hit.getBlockPos()) instanceof SignBlockEntity) + || !ClientPlayNetworking.canSend(BlackMarketInteractPayload.ID)) return false; + ClientPlayNetworking.send(new BlackMarketInteractPayload()); + return true; + } + private static void openInventorySmokeScreen(Minecraft client) { inventorySmokeScreenOpened = true; inventorySmokeOpenDelay = -1; diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/MinecraftMixin.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/MinecraftMixin.java index 46b2769..16c2913 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/MinecraftMixin.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/mixin/client/MinecraftMixin.java @@ -1,14 +1,21 @@ package fr.koka99cab.sanctuary26.sanctuary.mixin.client; +import fr.koka99cab.sanctuary26.sanctuary.client.SanctuaryClient; import net.minecraft.client.Minecraft; 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; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; /** Gives the game window the Sanctuary identity while preserving its connection suffix. */ @Mixin(Minecraft.class) public abstract class MinecraftMixin { + @Inject(method = "pickBlock", at = @At("HEAD"), cancellable = true) + private void sanctuary$blackMarketMiddleClick(CallbackInfo callback) { + if (SanctuaryClient.handleBlackMarketMiddleClick((Minecraft) (Object) this)) callback.cancel(); + } + @Inject(method = "createTitle", at = @At("RETURN"), cancellable = true) private void sanctuary$replaceMinecraftWindowTitle(CallbackInfoReturnable callback) { callback.setReturnValue(callback.getReturnValue().replaceFirst("^Minecraft\\*?", "Sanctuary")); diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/BlackMarketInteractPayload.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/BlackMarketInteractPayload.java new file mode 100644 index 0000000..09dcf97 --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/BlackMarketInteractPayload.java @@ -0,0 +1,27 @@ +package fr.koka99cab.sanctuary26.sanctuary.network; + +import fr.koka99cab.sanctuary26.sanctuary.SanctuaryMod; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +/** Empty middle-click intent; the server resolves and validates the targeted sign. */ +public record BlackMarketInteractPayload() implements CustomPacketPayload { + public static final Type ID = + new Type<>(SanctuaryMod.id("c2s/black_market_interact")); + public static final StreamCodec CODEC = + CustomPacketPayload.codec(BlackMarketInteractPayload::write, BlackMarketInteractPayload::new); + + private BlackMarketInteractPayload(RegistryFriendlyByteBuf buffer) { + this(); + } + + private void write(RegistryFriendlyByteBuf buffer) { + // No client position, listing ID, item or price is trusted. + } + + @Override + public Type type() { + return ID; + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java index be41a7a..7e665fb 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java @@ -22,7 +22,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 = 21; + public static final int PROTOCOL_VERSION = 22; private static boolean initialized; private SanctuaryNetworking() { @@ -53,6 +53,7 @@ public final class SanctuaryNetworking { PayloadTypeRegistry.serverboundPlay().register(ShopRequestPayload.ID, ShopRequestPayload.CODEC); PayloadTypeRegistry.serverboundPlay().register(ShopActionPayload.ID, ShopActionPayload.CODEC); PayloadTypeRegistry.serverboundPlay().register(BlackMarketCreatePayload.ID, BlackMarketCreatePayload.CODEC); + PayloadTypeRegistry.serverboundPlay().register(BlackMarketInteractPayload.ID, BlackMarketInteractPayload.CODEC); PayloadTypeRegistry.serverboundPlay().register(QuestRequestSubmitPayload.ID, QuestRequestSubmitPayload.CODEC); PayloadTypeRegistry.serverboundPlay().register(QuestRequestAcceptPayload.ID, QuestRequestAcceptPayload.CODEC); ServerPlayNetworking.registerGlobalReceiver(PurchaseProgressionPayload.ID, (payload, context) -> @@ -83,6 +84,8 @@ public final class SanctuaryNetworking { context.server().execute(() -> ShopService.handle(context.player(), payload))); ServerPlayNetworking.registerGlobalReceiver(BlackMarketCreatePayload.ID, (payload, context) -> context.server().execute(() -> BlackMarketService.create(context.player(), payload))); + ServerPlayNetworking.registerGlobalReceiver(BlackMarketInteractPayload.ID, (payload, context) -> + context.server().execute(() -> BlackMarketService.middleClick(context.player()))); ServerPlayNetworking.registerGlobalReceiver(QuestRequestSubmitPayload.ID, (payload, context) -> context.server().execute(() -> QuestRequestService.submit(context.player(), payload))); ServerPlayNetworking.registerGlobalReceiver(QuestRequestAcceptPayload.ID, (payload, context) -> diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketInteractionRules.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketInteractionRules.java new file mode 100644 index 0000000..a63f53c --- /dev/null +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketInteractionRules.java @@ -0,0 +1,16 @@ +package fr.koka99cab.sanctuary26.sanctuary.shop; + +/** Pure interaction gates shared by the server implementation and smoke tests. */ +final class BlackMarketInteractionRules { + private BlackMarketInteractionRules() { + } + + static boolean acceptsMiddleClick(boolean alive, boolean spectator, + boolean targetsSign, boolean mayInteract) { + return alive && !spectator && targetsSign && mayInteract; + } + + static boolean opensBlankListing(boolean middleClick, boolean secondaryUse, boolean heldItem) { + return heldItem && (middleClick || secondaryUse); + } +} diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java index 0161fa8..bbf233b 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketService.java @@ -75,7 +75,7 @@ public final class BlackMarketService { ? InteractionResult.SUCCESS : InteractionResult.PASS; } return player instanceof ServerPlayer serverPlayer - ? interact(serverPlayer, (ServerLevel) level, hit.getBlockPos(), sign) + ? interact(serverPlayer, (ServerLevel) level, hit.getBlockPos(), sign, false) : InteractionResult.PASS; }); PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> { @@ -101,8 +101,22 @@ public final class BlackMarketService { BlackMarketCommands.register(dispatcher)); } + public static void middleClick(ServerPlayer player) { + net.minecraft.world.phys.HitResult hit = player.pick( + player.blockInteractionRange() + 1.0D, 1.0F, false); + boolean targetsSign = hit instanceof net.minecraft.world.phys.BlockHitResult blockHit + && player.level().getBlockEntity(blockHit.getBlockPos()) instanceof SignBlockEntity; + BlockPos pos = targetsSign + ? ((net.minecraft.world.phys.BlockHitResult) hit).getBlockPos() : BlockPos.ZERO; + boolean mayInteract = targetsSign && player.level().mayInteract(player, pos); + if (!BlackMarketInteractionRules.acceptsMiddleClick( + player.isAlive(), player.isSpectator(), targetsSign, mayInteract)) return; + SignBlockEntity sign = (SignBlockEntity) player.level().getBlockEntity(pos); + interact(player, (ServerLevel) player.level(), pos, sign, true); + } + private static InteractionResult interact(ServerPlayer player, ServerLevel level, BlockPos pos, - SignBlockEntity sign) { + SignBlockEntity sign, boolean middleClick) { BlackMarketData data = BlackMarketData.get(level.getServer()); Optional existing = data.listingAt(dimension(level), pos.asLong()); if (existing.isPresent()) { @@ -115,7 +129,8 @@ public final class BlackMarketService { } return InteractionResult.SUCCESS_SERVER; } - if (!player.isSecondaryUseActive() || player.getMainHandItem().isEmpty()) return InteractionResult.PASS; + if (!BlackMarketInteractionRules.opensBlankListing(middleClick, + player.isSecondaryUseActive(), !player.getMainHandItem().isEmpty())) return InteractionResult.PASS; if (data.isBanned(player.getUUID())) { player.sendSystemMessage(Component.translatable("message.sanctuary.black_market.banned")); return InteractionResult.CONSUME; diff --git a/sanctuary/src/main/resources/fabric.mod.json b/sanctuary/src/main/resources/fabric.mod.json index b7cd986..4c86231 100644 --- a/sanctuary/src/main/resources/fabric.mod.json +++ b/sanctuary/src/main/resources/fabric.mod.json @@ -33,7 +33,7 @@ "system_type": "gameplay-system", "namespace": "sanctuary", "data_version": 11, - "network_protocol": 21, + "network_protocol": 22, "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", "descriptions": { diff --git a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketModelSmoke.java b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketModelSmoke.java index c984996..d7349de 100644 --- a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketModelSmoke.java +++ b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/BlackMarketModelSmoke.java @@ -8,6 +8,19 @@ public final class BlackMarketModelSmoke { private BlackMarketModelSmoke() {} public static void main(String[] args) { + require(BlackMarketInteractionRules.acceptsMiddleClick(true, false, true, true), + "A valid middle-click sign target was rejected"); + require(!BlackMarketInteractionRules.acceptsMiddleClick(false, false, true, true), + "A dead player may middle-click the Black Market"); + require(!BlackMarketInteractionRules.acceptsMiddleClick(true, true, true, true), + "A spectator may middle-click the Black Market"); + require(!BlackMarketInteractionRules.acceptsMiddleClick(true, false, false, true), + "A non-sign target may open the Black Market"); + require(BlackMarketInteractionRules.opensBlankListing(true, false, true), + "Middle-click should create a listing without crouching"); + require(!BlackMarketInteractionRules.opensBlankListing(true, false, false), + "An empty hand should not create a listing"); + UUID seller = UUID.fromString("6c43c35a-3498-41bb-a77e-10c679e635d1"); ItemStack escrow = ItemStack.EMPTY; BlackMarketData data = new BlackMarketData();