diff --git a/CHANGELOG.md b/CHANGELOG.md index 98ab14b..9be3bbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ 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.199` + +Module modifié : **Sanctuary `0.0.0-alpha.115`**. Les neuf autres modules conservent leur version issue de l’alpha.198. + +- donne au panneau communautaire de demandes une texture rose framboise dédiée, sans changer ses IDs debout et mural ; +- réduit le texte visible sur le panneau au demandeur, à l’état disponible/prise et à l’invitation à lire les détails ; +- remplace la réservation immédiate par un aperçu détaillé dans le chat puis un bouton cliquable de confirmation ; +- revalide lors de cette confirmation la distance, la dimension, le panneau physique, le propriétaire et l’absence de réservation concurrente ; +- complète l’éditeur avec une cible KILL de type mob ou joueur et conserve BRING sur un item et PAY sur un joueur ; +- ajoute une récompense promise obligatoire pour les nouvelles demandes, sous forme de don, paiement ou prêt d’un item et d’une quantité validés côté serveur ; +- présente séparément dans le chat le demandeur, l’objectif, la mission, la cible, la quantité, la récompense et l’éventuel preneur ; +- migre monotoniquement `quest_requests` de la donnée v1 à v2 ; les anciennes demandes restent lisibles avec une récompense non renseignée ; +- porte le protocole Sanctuary de 19 à 20 pour les nouveaux champs bornés, sans automatiser la remise de récompense, le retour d’un prêt ou la complétion ; +- conserve les données Sanctuary v11, tous les IDs de registre et les sauvegardes existantes. + ## `26.2.0-alpha.198` Modules modifiés : **It's Alive `0.0.0-alpha.30`** et **Sanctuary `0.0.0-alpha.114`**. Les huit autres modules restent inchangés. diff --git a/README.md b/README.md index 64f9202..048febd 100644 --- a/README.md +++ b/README.md @@ -22,7 +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 local : **`26.2.0-alpha.198`**. It's Alive passe en **`0.0.0-alpha.30`** et Sanctuary en **`0.0.0-alpha.114`** ; les huit autres modules restent inchangés. +Version courante du pack local : **`26.2.0-alpha.199`**. It's Alive est en **`0.0.0-alpha.30`** et Sanctuary passe en **`0.0.0-alpha.115`** ; les huit autres modules restent inchangés. + +L’alpha.199 rend le panneau communautaire immédiatement identifiable grâce à une texture rose dédiée et garde son texte minimal. Un clic droit affiche désormais dans le chat l’objectif complet, la cible mob/item/joueur et la récompense promise sous forme de don, paiement ou prêt ; la quête n’est prise qu’après confirmation sur le bouton du chat. Les contrats restent communautaires : le serveur valide et conserve leurs termes sans simuler une remise, une restitution ou une complétion automatique. L’alpha.198 borne l’automatisation du tonneau de fermentation : chaque emplacement n’accepte qu’une unité utile à une recette encore réalisable, les doublons invalides sont refusés et les observateurs suivent les changements visibles du contenu sans impulsion de progression parasite. Elle intègre aussi la correction de Chris : le shift-clic de l’inventaire suit le comportement vanilla sur toutes les rangées débloquées, y compris les rangées 5 et 6, tandis qu’une rangée de stockage ou d’overflow n’alimente que la hotbar. @@ -34,7 +36,7 @@ L’alpha.195 charge le catalogue Shop depuis le CSV rééquilibré et conserve L’alpha.194 ouvre la cuisine avec huit cultures supplémentaires — riz, soja, haricot, pois chiche, piment, basilic, menthe et coriandre — ainsi que le café, la vanille, l’olivier et le citronnier. Le riz pousse réellement immergé sur de la boue, le haricot se récolte jeune ou sec, le café se torréfie et la vanille se sèche au smoker. Le climat détermine seulement la découverte : les nouvelles plantes sauvages apparaissent dans les nouveaux chunks, tandis qu’une chance rare de semence sur la végétation permet de continuer un monde existant. Aucune réinitialisation de sauvegarde n’est requise. -Le [wiki cuisine de l’alpha.197](itsalive/FOOD_WIKI.md) détaille les cultures, les six postes, l’automatisation, les ingrédients transformés, le livre et ses 64 recettes. +Le [wiki cuisine de l’alpha.199](itsalive/FOOD_WIKI.md) détaille les cultures, les six postes, l’automatisation, les ingrédients transformés, le livre et ses 64 recettes. L’alpha.193 donne à chaque item du Shop une petite variation de prix stable et reproductible, tout en réservant des tarifs explicites aux vrais objets exceptionnels. L’œuf doré, porte d’entrée extrêmement rare vers les poules à gemmes, coûte désormais 811 rubis ou 59 saphirs dans le catalogue. Les six emplacements verrouillés occupent directement les six cartes restantes de la grille : ils se débloquent toujours dans l’ordre pour 2, 4, 8, 12, 18 puis 25 niveaux. L’aide de boîte aux lettres remonte légèrement et la bourse rubis/saphirs s’ancre en bas à droite. diff --git a/build.gradle b/build.gradle index 05a3621..5fe28f4 100644 --- a/build.gradle +++ b/build.gradle @@ -3427,8 +3427,8 @@ tasks.register("verifyProgressivePauseMenuRelease") { [(locale): new JsonSlurper().parse(file( "sanctuary/src/main/resources/assets/sanctuary/lang/${locale}.json"))] } - if (rootProject.pack_version != "26.2.0-alpha.198" - || rootProject.sanctuary_version != "0.0.0-alpha.114" + if (rootProject.pack_version != "26.2.0-alpha.199" + || rootProject.sanctuary_version != "0.0.0-alpha.115" || rootProject.onlyfun_version != "0.0.0-alpha.19" || 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"] @@ -3477,7 +3477,7 @@ tasks.register("verifyMirrorLavenderDrawerRelease") { def redStonerRegistry = file( "redstoner/src/main/java/fr/koka99cab/sanctuary26/redstoner/registry/RedStonerRegistries.java").text def drawerRecipe = file("redstoner/src/main/resources/data/redstoner/recipe/drawer.json") - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || rootProject.anotherworld_version != "0.0.0-alpha.49" || rootProject.redstoner_version != "0.0.0-alpha.10" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version @@ -4121,9 +4121,9 @@ tasks.register("verifyShopPricingLayoutRelease") { "pack/migrations/26.2.0-alpha.192-shop-pricing-layout-to-alpha.193.json")) def release = new JsonSlurper().parse(file("pack/release.json")) def index = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || rootProject.onlyfun_version != "0.0.0-alpha.19" - || rootProject.sanctuary_version != "0.0.0-alpha.114" + || rootProject.sanctuary_version != "0.0.0-alpha.115" || 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" @@ -4166,7 +4166,7 @@ tasks.register("verifyCuisineCropsRelease") { def climate = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/ClimateCropForaging.java").text def rice = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/RiceCropBlock.java").text def bean = file("itsalive/src/main/java/fr/koka99cab/sanctuary26/itsalive/agriculture/BeanCropBlock.java").text - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || rootProject.itsalive_version != "0.0.0-alpha.30" || release.pack_version != rootProject.pack_version || index.versionId != rootProject.pack_version || migration.source?.pack_version != "26.2.0-alpha.193" @@ -4215,9 +4215,9 @@ tasks.register("verifyShopCsvBossGatesRelease") { def bossGate = file("onlyfun/src/main/java/fr/koka99cab/sanctuary26/onlyfun/shop/OnlyFunBossShopGate.java").text def catalog = file("onlyfun/src/main/java/fr/koka99cab/sanctuary26/onlyfun/shop/OnlyFunShopCatalog.java").text def offerCatalog = file("sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/ShopOfferCatalog.java").text - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || rootProject.onlyfun_version != "0.0.0-alpha.19" - || rootProject.sanctuary_version != "0.0.0-alpha.114" + || rootProject.sanctuary_version != "0.0.0-alpha.115" || 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"] @@ -4261,7 +4261,7 @@ tasks.register("verifyCulinaryMailboxGrappleFixesRelease") { def release = new JsonSlurper().parse(file("pack/release.json")) def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) def packwiz = file("pack/packwiz/pack.toml").text - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || migration.source?.pack_version != "26.2.0-alpha.195" || migration.target?.pack_version != "26.2.0-alpha.196" || migration.target?.modules != [ @@ -4279,10 +4279,10 @@ tasks.register("verifyCulinaryMailboxGrappleFixesRelease") { || migration.distribution?.jei?.target != "30.26.0.182" || migration.compatibility?.save_migration_required != false || migration.compatibility?.registry_ids_changed != false - || release.pack_version != "26.2.0-alpha.198" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.198.mrpack" - || prism.versionId != "26.2.0-alpha.198" - || !packwiz.contains('version = "26.2.0-alpha.198"')) { + || release.pack_version != "26.2.0-alpha.199" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.199.mrpack" + || prism.versionId != "26.2.0-alpha.199" + || !packwiz.contains('version = "26.2.0-alpha.199"')) { throw new GradleException("The alpha.196 culinary, mailbox and grapple release contract is incomplete") } } @@ -4307,9 +4307,9 @@ tasks.register("verifyQuestRequestsCauldronBatchesRelease") { def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) def packwiz = file("pack/packwiz/pack.toml").text def sanctuaryManifest = new JsonSlurper().parse(file("sanctuary/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || rootProject.itsalive_version != "0.0.0-alpha.30" - || rootProject.sanctuary_version != "0.0.0-alpha.114" + || rootProject.sanctuary_version != "0.0.0-alpha.115" || 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"] @@ -4326,11 +4326,11 @@ 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 != 19 - || release.pack_version != "26.2.0-alpha.198" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.198.mrpack" - || prism.versionId != "26.2.0-alpha.198" - || !packwiz.contains('version = "26.2.0-alpha.198"')) { + || sanctuaryManifest.custom?.sanctuary26?.network_protocol != 20 + || release.pack_version != "26.2.0-alpha.199" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.199.mrpack" + || prism.versionId != "26.2.0-alpha.199" + || !packwiz.contains('version = "26.2.0-alpha.199"')) { throw new GradleException("The alpha.197 quest-request and cauldron-batch release contract is incomplete") } } @@ -4357,7 +4357,7 @@ tasks.register("verifyFermentationBarrelAutomationRelease") { def packwiz = file("pack/packwiz/pack.toml").text def packwizIndex = file("pack/packwiz/index.toml").text def itsAliveManifest = new JsonSlurper().parse(file("itsalive/src/main/resources/fabric.mod.json")) - if (rootProject.pack_version != "26.2.0-alpha.198" + if (rootProject.pack_version != "26.2.0-alpha.199" || rootProject.itsalive_version != "0.0.0-alpha.30" || migration.source?.pack_version != "26.2.0-alpha.197" || migration.target?.pack_version != "26.2.0-alpha.198" @@ -4376,10 +4376,10 @@ tasks.register("verifyFermentationBarrelAutomationRelease") { || migration.distribution?.packwiz_index_refreshed != true || itsAliveManifest.custom?.sanctuary26?.data_version != 1 || itsAliveManifest.custom?.sanctuary26?.network_protocol != 3 - || release.pack_version != "26.2.0-alpha.198" - || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.198.mrpack" - || prism.versionId != "26.2.0-alpha.198" - || !packwiz.contains('version = "26.2.0-alpha.198"') + || release.pack_version != "26.2.0-alpha.199" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.199.mrpack" + || prism.versionId != "26.2.0-alpha.199" + || !packwiz.contains('version = "26.2.0-alpha.199"') || !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.30.jar"')) { throw new GradleException("The alpha.198 fermentation barrel release contract is incomplete") } @@ -4390,6 +4390,52 @@ tasks.named("verifyWorkspace") { dependsOn tasks.named("verifyFermentationBarrelAutomationRelease") } +tasks.register("verifyQuestRequestContractsRelease") { + group = "verification" + description = "Checks the complete Sanctuary 26.2.0-alpha.199 quest-request contract release." + inputs.file(file("pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json")) + inputs.file(file("pack/release.json")) + inputs.file(file("pack/prism/modrinth.index.json")) + inputs.file(file("pack/packwiz/pack.toml")) + inputs.file(file("pack/packwiz/index.toml")) + inputs.file(file("sanctuary/src/main/resources/fabric.mod.json")) + doLast { + def migration = new JsonSlurper().parse(file( + "pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json")) + def release = new JsonSlurper().parse(file("pack/release.json")) + def prism = new JsonSlurper().parse(file("pack/prism/modrinth.index.json")) + def packwiz = file("pack/packwiz/pack.toml").text + 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.199" + || rootProject.itsalive_version != "0.0.0-alpha.30" + || rootProject.sanctuary_version != "0.0.0-alpha.115" + || 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" + || migration.target?.modules?.sanctuary != "0.0.0-alpha.115" + || migration.quest_request_contracts?.target_data_version != 2 + || migration.quest_request_contracts?.kill_target_kinds != ["mob", "player"] + || migration.quest_request_contracts?.reward_types != ["gift", "payment", "loan"] + || 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 != 20 + || release.pack_version != "26.2.0-alpha.199" + || release.modrinth?.primary_file != "sanctuary-26.2.0-alpha.199.mrpack" + || prism.versionId != "26.2.0-alpha.199" + || !packwiz.contains('version = "26.2.0-alpha.199"') + || !packwizIndex.contains('file = "mods/itsalive-0.0.0-alpha.30.jar"') + || !packwizIndex.contains('file = "mods/sanctuary-0.0.0-alpha.115.jar"')) { + throw new GradleException("The alpha.199 quest-request contract release is incomplete") + } + } +} + +tasks.named("verifyWorkspace") { + dependsOn tasks.named("verifyQuestRequestContractsRelease") +} + tasks.named("check") { dependsOn tasks.named("verifyWorkspace") dependsOn tasks.named("verifyTransport") diff --git a/gradle.properties b/gradle.properties index 569d5d9..3e69b32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,7 +28,7 @@ iliketomoveit_version=0.0.0-alpha.13 iliketomoveit_lifecycle=active onlyfun_version=0.0.0-alpha.19 onlyfun_lifecycle=active -sanctuary_version=0.0.0-alpha.114 +sanctuary_version=0.0.0-alpha.115 sanctuary_lifecycle=active -pack_version=26.2.0-alpha.198 +pack_version=26.2.0-alpha.199 maven_group=fr.koka99cab.sanctuary26 diff --git a/pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json b/pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json new file mode 100644 index 0000000..be59ba4 --- /dev/null +++ b/pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json @@ -0,0 +1,46 @@ +{ + "schema_version": 1, + "source": { + "pack_version": "26.2.0-alpha.198", + "modules": { + "sanctuary": "0.0.0-alpha.114" + } + }, + "target": { + "pack_version": "26.2.0-alpha.199", + "modules": { + "sanctuary": "0.0.0-alpha.115" + } + }, + "quest_request_contracts": { + "standing_id": "sanctuary:quest_request_sign", + "wall_id": "sanctuary:quest_request_wall_sign", + "save_key": "quest_requests", + "source_data_version": 1, + "target_data_version": 2, + "existing_requests_default_reward": "none", + "new_requests_require_reward": true, + "kill_target_kinds": ["mob", "player"], + "reward_types": ["gift", "payment", "loan"], + "reward_item_server_validated": true, + "reward_transfer_or_completion_automated": false, + "right_click_shows_details_before_claim": true, + "chat_accept_command": "sanctuary_quest_accept", + "chat_accept_revalidates_distance_owner_sign_and_claim": true, + "minimal_sign_text": true, + "pink_block_texture": "sanctuary:block/pink_quest_request_sign", + "pink_item_texture": "sanctuary:item/pink_quest_request_sign" + }, + "distribution": { + "prism_instance": "Sanctuary-26.2-dev", + "packwiz_index_refreshed": true + }, + "compatibility": { + "sanctuary_data_version": 11, + "sanctuary_source_network_protocol": 19, + "sanctuary_target_network_protocol": 20, + "existing_requests_preserved": true, + "save_migration_monotone": true, + "registry_ids_changed": false + } +} diff --git a/pack/packwiz/index.toml b/pack/packwiz/index.toml index a3e7b9f..396a725 100644 --- a/pack/packwiz/index.toml +++ b/pack/packwiz/index.toml @@ -14,15 +14,15 @@ hash = "41f4f4c88c71d0a23446d5014f7407d55ad477441847099cad24492b032335a0" [[files]] file = "mods/ambiance-0.0.0-alpha.11.jar" -hash = "70c23a4e4059eaf52f99e4a31585169c6599b4e33ad7554cddbbec536096cc64" +hash = "12e63938bba723137faa9f3a416ab8e4068b0297230cfc39a2031cdd439aa784" [[files]] file = "mods/anotherworld-0.0.0-alpha.49.jar" -hash = "69f6c36628503f5abf04ec3f9e8351d7b3e5468829573733590641a9078760d0" +hash = "da09dbbd260d913e6a6aebd7759883ccdb408481bc0392db8063e82b1b89c95b" [[files]] file = "mods/events-0.0.0-alpha.0.jar" -hash = "2cd283f8b85055a405947a41325f9780b98046b659a24a95c3279d96c97205d5" +hash = "85ca0dafb7851077a0ab7fe86e9ec9897d73befe9b658f1871969c3a1ebacf59" [[files]] file = "mods/fabric-api.pw.toml" @@ -31,7 +31,7 @@ metafile = true [[files]] file = "mods/iliketomoveit-0.0.0-alpha.13.jar" -hash = "b1b33c5fe7089b8d1baac4ce5fbd0ee386def1456b71af6f761d6ae800e61690" +hash = "138350c1f085a97d3ff1119749024cc73c67f780ab359eed69d31d44a7e2471e" [[files]] file = "mods/iris.pw.toml" @@ -40,7 +40,7 @@ metafile = true [[files]] file = "mods/itsalive-0.0.0-alpha.30.jar" -hash = "57a7a91b47c5648710fa165a894c785c5958afe07f7d7f4e3ef4d0cc38cacd8b" +hash = "747e5e6d61c87f68c5b9efe3eaa54daa511f044dc964b0352d9212766c06f1bf" [[files]] file = "mods/jei.pw.toml" @@ -49,23 +49,23 @@ metafile = true [[files]] file = "mods/masterkey-0.0.0-alpha.8.jar" -hash = "90803d3a67065ee02cca4fc32c8f18301e93e1ff2c4c8002feb5f28c7ec014da" +hash = "3ab2dac208babbd6d503a25c7d41a91d337f15f30f9da5c25988b9a5daa1b3d3" [[files]] file = "mods/onlyfun-0.0.0-alpha.19.jar" -hash = "86c7f88ec348f678cb873d2386df38ca300928f4b0e7c350cae722ba42389986" +hash = "be2b83a6033df420887d643b91c5bdfee5f3bb3933f156e36f256cd59c0fc083" [[files]] file = "mods/ouch-0.0.0-alpha.8.jar" -hash = "bd57342e13832fe67e7abeb0f2768170552fd868668a6f6940cd73f57dd7b37c" +hash = "b77e0b00aa2be6bab0fb9d3d749758b492323e756d1a9848ee7d7776fedf8afb" [[files]] file = "mods/redstoner-0.0.0-alpha.10.jar" -hash = "2259c2a982c14d29f27c7a0908aeee11b7156b2823202a200aa89af24703eb23" +hash = "a51b166afef4a687ca77a7a3e149b008adc94dbe6a5d387ed27ec5a01caaf2f9" [[files]] -file = "mods/sanctuary-0.0.0-alpha.114.jar" -hash = "7e45eb1a6756ed9a95d79d794b7c2e7a6918388fd9e5286160cd890d1dfaf0f6" +file = "mods/sanctuary-0.0.0-alpha.115.jar" +hash = "d57eb30bd5235a0d9fe5f26eb486362bc71a2a90e01f6f248ae2e8ab7a8ba935" [[files]] file = "mods/simple-voice-chat.pw.toml" diff --git a/pack/packwiz/pack.toml b/pack/packwiz/pack.toml index 66b4c89..7304699 100644 --- a/pack/packwiz/pack.toml +++ b/pack/packwiz/pack.toml @@ -1,12 +1,12 @@ name = "Sanctuary 26.2" author = "KOKA99CAB" -version = "26.2.0-alpha.198" +version = "26.2.0-alpha.199" pack-format = "packwiz:1.1.0" [index] file = "index.toml" hash-format = "sha256" -hash = "55cce7f3d86dbb8546626ea5f3ed5596efa656ab41f528d8b7ed963470b81219" +hash = "4365a5e895a893ef3c81ba895412563fbb336b217cd2301a5830ec132efb8886" [versions] fabric = "0.19.3" diff --git a/pack/prism/modrinth.index.json b/pack/prism/modrinth.index.json index eca64f8..bda15e4 100644 --- a/pack/prism/modrinth.index.json +++ b/pack/prism/modrinth.index.json @@ -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.198", + "versionId": "26.2.0-alpha.199", "files": [ { "path": "mods/fabric-api-0.158.0+26.2.jar", diff --git a/pack/release.json b/pack/release.json index cd303e6..334d06b 100644 --- a/pack/release.json +++ b/pack/release.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "pack_version": "26.2.0-alpha.198", + "pack_version": "26.2.0-alpha.199", "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.198.mrpack", + "primary_file": "sanctuary-26.2.0-alpha.199.mrpack", "shared_client_server_pack": true, "update_owner": "packwiz-installer", "check_on_launch": true, diff --git a/sanctuary/build.gradle b/sanctuary/build.gradle index 94c88ba..bfe2fe8 100644 --- a/sanctuary/build.gradle +++ b/sanctuary/build.gradle @@ -1052,12 +1052,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 != 19 + || manifest.custom?.sanctuary26?.network_protocol != 20 || 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 19, JEI and the client entrypoint") + throw new GradleException("Sanctuary must expose data v11, protocol 20, 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")) @@ -2886,7 +2886,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.114" + if (project.version.toString() != "0.0.0-alpha.115" || 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 @@ -2924,7 +2924,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.114" + if (project.version.toString() != "0.0.0-alpha.115" || 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] @@ -2955,7 +2955,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.114" + if (project.version.toString() != "0.0.0-alpha.115" || migration.target?.pack_version != "26.2.0-alpha.195" || migration.target?.modules?.sanctuary != "0.0.0-alpha.111" || migration.compatibility?.sanctuary_data_version != 11 @@ -2986,7 +2986,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.114" + if (project.version.toString() != "0.0.0-alpha.115" || migration.target?.modules?.sanctuary != "0.0.0-alpha.112" || migration.mailbox?.id != "sanctuary:delivery_box" || migration.mailbox?.horizontal_facing != true @@ -3014,7 +3014,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.114" + if (project.version.toString() != "0.0.0-alpha.115" || !transfer.contains("shouldAppendOverflow") || !transfer.contains("isVanillaStorageRange") || !transfer.contains("hotbarStart") @@ -3060,7 +3060,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.114" + if (project.version.toString() != "0.0.0-alpha.115" || migration.target?.modules?.sanctuary != "0.0.0-alpha.113" || migration.quest_requests?.save_key != "quest_requests" || migration.quest_requests?.data_version != 1 @@ -3069,10 +3069,10 @@ tasks.register("verifyQuestRequests") { || migration.quest_requests?.drops_recoverable_item != true || migration.quest_requests?.payloads?.server_validated_and_bounded != true || migration.compatibility?.sanctuary_target_network_protocol != 19 - || !data.contains('SAVE_KEY = "quest_requests"') || !data.contains("DATA_VERSION = 1") + || !data.contains('SAVE_KEY = "quest_requests"') || !data.contains("DATA_VERSION = 2") || !service.contains("PlayerBlockBreakEvents.BEFORE") || !service.contains("player.blockPosition().distSqr(payload.pos()) > 64.0D") - || !network.contains("PROTOCOL_VERSION = 19") + || !network.contains("PROTOCOL_VERSION = 20") || !network.contains("QuestRequestSubmitPayload.ID") || !registry.contains('SanctuaryMod.id("quest_request_sign")') || lootIds != ["sanctuary:quest_request_sign", "sanctuary:quest_request_sign"] @@ -3086,6 +3086,75 @@ tasks.register("verifyQuestRequests") { } } +tasks.register("verifyQuestRequestContracts") { + group = "verification" + description = "Checks alpha.115 pink quest-request previews, player targets and promised rewards." + inputs.file(rootProject.file("pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestData.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestService.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/QuestRequestEditorScreen.java")) + inputs.file(file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java")) + inputs.files(fileTree("src/main/resources/assets/sanctuary/lang") { include "*.json" }) + inputs.files(fileTree("src/main/resources/assets/sanctuary/models") { include "**/quest_request*.json" }) + inputs.files(file( + "src/main/resources/assets/sanctuary/textures/block/pink_quest_request_sign.png"), file( + "src/main/resources/assets/sanctuary/textures/item/pink_quest_request_sign.png")) + doLast { + def migration = new JsonSlurper().parse(rootProject.file( + "pack/migrations/26.2.0-alpha.198-quest-request-contracts-to-alpha.199.json")) + def data = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestData.java").text + def service = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestService.java").text + def screen = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/QuestRequestEditorScreen.java").text + def network = file("src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/SanctuaryNetworking.java").text + def standingModel = new JsonSlurper().parse(file( + "src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_0.json")) + def itemModel = new JsonSlurper().parse(file( + "src/main/resources/assets/sanctuary/models/item/quest_request_sign.json")) + def blockTexture = ImageIO.read(file( + "src/main/resources/assets/sanctuary/textures/block/pink_quest_request_sign.png")) + def itemTexture = ImageIO.read(file( + "src/main/resources/assets/sanctuary/textures/item/pink_quest_request_sign.png")) + 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.115" + || 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" + || migration.target?.modules?.sanctuary != "0.0.0-alpha.115" + || migration.quest_request_contracts?.source_data_version != 1 + || migration.quest_request_contracts?.target_data_version != 2 + || migration.quest_request_contracts?.kill_target_kinds != ["mob", "player"] + || migration.quest_request_contracts?.reward_types != ["gift", "payment", "loan"] + || 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 + || !data.contains("DATA_VERSION = 2") + || !data.contains('optionalFieldOf("reward_type"') + || !service.contains('Commands.literal("sanctuary_quest_accept")') + || !service.contains("new ClickEvent.RunCommand") + || !service.contains('REWARD_LOAN = "loan"') + || !service.contains('PLAYER.equals(targetKind)') + || !screen.contains("rewardQuantity") || !screen.contains("killTargetIndex") + || !network.contains("PROTOCOL_VERSION = 20") + || 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 + || !blockTexture.colorModel.hasAlpha() + || itemTexture == null || itemTexture.width != 16 || itemTexture.height != 16 + || !itemTexture.colorModel.hasAlpha() + || locales.any { locale, values -> + !values.containsKey("screen.sanctuary.quest_request.reward_type.loan") + || !values.containsKey("screen.sanctuary.quest_request.target_hint.kill.player") + || !values.containsKey("message.sanctuary.quest_request.accept") + || !values.containsKey("message.sanctuary.quest_request.details.reward") + || !values.containsKey("sign.sanctuary.quest_request.details") + }) { + throw new GradleException("Sanctuary alpha.115 quest-request contract is incomplete") + } + } +} + tasks.named("verifySanctuary") { dependsOn tasks.named("verifyInventoryShiftClick") } @@ -3094,6 +3163,7 @@ tasks.named("check") { dependsOn tasks.named("verifySanctuary") dependsOn tasks.named("verifyDeliveryBoxOrientation") dependsOn tasks.named("verifyQuestRequests") + dependsOn tasks.named("verifyQuestRequestContracts") dependsOn tasks.named("celestialGraphSmoke") dependsOn tasks.named("questRequestModelSmoke") } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/QuestRequestEditorScreen.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/QuestRequestEditorScreen.java index a82c60b..e459b16 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/QuestRequestEditorScreen.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/client/QuestRequestEditorScreen.java @@ -10,20 +10,30 @@ import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; -/** Bounded editor for community quest requests; the server remains authoritative. */ +/** Complete bounded editor for mission target and promised reward contracts. */ public final class QuestRequestEditorScreen extends Screen { private static final String[] ACTIONS = { QuestRequestService.KILL, QuestRequestService.BRING, QuestRequestService.PAY }; + private static final String[] REWARDS = { + QuestRequestService.REWARD_GIFT, QuestRequestService.REWARD_PAYMENT, + QuestRequestService.REWARD_LOAN + }; private final Screen parent; private final QuestRequestEditorPayload payload; private EditBox alias; private EditBox objective; private EditBox target; private EditBox quantity; + private EditBox rewardTarget; + private EditBox rewardQuantity; private Button action; + private Button targetKind; + private Button reward; private Button submit; private int actionIndex; + private int rewardIndex; + private int killTargetIndex; public QuestRequestEditorScreen(Screen parent, QuestRequestEditorPayload payload) { super(Component.translatable("screen.sanctuary.quest_request.title")); @@ -32,18 +42,22 @@ public final class QuestRequestEditorScreen extends Screen { for (int index = 0; index < ACTIONS.length; index++) { if (ACTIONS[index].equals(payload.action())) actionIndex = index; } + for (int index = 0; index < REWARDS.length; index++) { + if (REWARDS[index].equals(payload.rewardType())) rewardIndex = index; + } + killTargetIndex = QuestRequestService.PLAYER.equals(payload.targetKind()) ? 1 : 0; } @Override protected void init() { int center = width / 2; - alias = addRenderableWidget(new EditBox(font, center - 116, 61, 232, 20, + alias = addRenderableWidget(new EditBox(font, center - 152, 48, 304, 20, Component.translatable("screen.sanctuary.quest_request.alias"))); alias.setMaxLength(QuestRequestService.MAX_ALIAS_LENGTH); alias.setValue(payload.alias()); alias.setHint(Component.translatable("screen.sanctuary.quest_request.alias_hint")); - objective = addRenderableWidget(new EditBox(font, center - 116, 101, 232, 20, + objective = addRenderableWidget(new EditBox(font, center - 152, 83, 304, 20, Component.translatable("screen.sanctuary.quest_request.objective"))); objective.setMaxLength(QuestRequestService.MAX_OBJECTIVE_LENGTH); objective.setValue(payload.objective()); @@ -52,24 +66,47 @@ public final class QuestRequestEditorScreen extends Screen { action = addRenderableWidget(Button.builder(actionLabel(), button -> { actionIndex = (actionIndex + 1) % ACTIONS.length; button.setMessage(actionLabel()); - target.setHint(targetHint()); - }).bounds(center - 116, 141, 76, 20).build()); + refreshMissionControls(); + }).bounds(center - 152, 118, 76, 20).build()); - quantity = addRenderableWidget(new EditBox(font, center - 34, 141, 48, 20, + targetKind = addRenderableWidget(Button.builder(targetKindLabel(), button -> { + if (!QuestRequestService.KILL.equals(currentAction())) return; + killTargetIndex = (killTargetIndex + 1) % 2; + refreshMissionControls(); + }).bounds(center - 72, 118, 70, 20).build()); + + quantity = addRenderableWidget(new EditBox(font, center + 2, 118, 46, 20, Component.translatable("screen.sanctuary.quest_request.quantity"))); quantity.setMaxLength(4); quantity.setValue(Integer.toString(payload.quantity())); - target = addRenderableWidget(new EditBox(font, center + 20, 141, 96, 20, + target = addRenderableWidget(new EditBox(font, center + 52, 118, 100, 20, Component.translatable("screen.sanctuary.quest_request.target"))); target.setMaxLength(QuestRequestService.MAX_TARGET_LENGTH); target.setValue(payload.target()); - target.setHint(targetHint()); + + reward = addRenderableWidget(Button.builder(rewardLabel(), button -> { + rewardIndex = (rewardIndex + 1) % REWARDS.length; + button.setMessage(rewardLabel()); + rewardTarget.setHint(rewardHint()); + }).bounds(center - 152, 157, 84, 20).build()); + + rewardQuantity = addRenderableWidget(new EditBox(font, center - 64, 157, 46, 20, + Component.translatable("screen.sanctuary.quest_request.reward_quantity"))); + rewardQuantity.setMaxLength(4); + rewardQuantity.setValue(Integer.toString(payload.rewardQuantity())); + + rewardTarget = addRenderableWidget(new EditBox(font, center - 14, 157, 166, 20, + Component.translatable("screen.sanctuary.quest_request.reward_target"))); + rewardTarget.setMaxLength(QuestRequestService.MAX_TARGET_LENGTH); + rewardTarget.setValue(payload.rewardTarget()); + rewardTarget.setHint(rewardHint()); submit = addRenderableWidget(Button.builder(Component.translatable("screen.sanctuary.quest_request.publish"), - button -> submit()).bounds(center - 116, 178, 113, 20).build()); + button -> submit()).bounds(center - 152, 199, 149, 20).build()); addRenderableWidget(Button.builder(Component.translatable("gui.cancel"), button -> onClose()) - .bounds(center + 3, 178, 113, 20).build()); + .bounds(center + 3, 199, 149, 20).build()); + refreshMissionControls(); setInitialFocus(alias); } @@ -81,38 +118,68 @@ public final class QuestRequestEditorScreen extends Screen { @Override public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float tickDelta) { int center = width / 2; - graphics.fill(center - 128, 24, center + 128, 210, 0xD0101010); - graphics.outline(center - 128, 24, 256, 186, 0xFF4E9B62); - graphics.centeredText(font, title, center, 33, 0xFF7BE495); + graphics.fill(center - 164, 18, center + 164, 226, 0xD0101010); + graphics.outline(center - 164, 18, 328, 208, 0xFFE83E8C); + graphics.centeredText(font, title, center, 25, 0xFFFF74B8); graphics.text(font, Component.translatable("screen.sanctuary.quest_request.alias"), - center - 116, 50, 0xFFB9B9B9); + center - 152, 37, 0xFFDBB7CA); graphics.text(font, Component.translatable("screen.sanctuary.quest_request.objective"), - center - 116, 90, 0xFFB9B9B9); - graphics.text(font, Component.translatable("screen.sanctuary.quest_request.action"), - center - 116, 130, 0xFFB9B9B9); - graphics.text(font, Component.translatable("screen.sanctuary.quest_request.quantity"), - center - 34, 130, 0xFFB9B9B9); - graphics.text(font, Component.translatable("screen.sanctuary.quest_request.target"), - center + 20, 130, 0xFFB9B9B9); + center - 152, 72, 0xFFDBB7CA); + graphics.text(font, Component.translatable("screen.sanctuary.quest_request.mission"), + center - 152, 107, 0xFFDBB7CA); + graphics.text(font, Component.translatable("screen.sanctuary.quest_request.reward"), + center - 152, 146, 0xFFFF74B8); graphics.centeredText(font, Component.translatable("screen.sanctuary.quest_request.help"), - center, 164, 0xFF8F8F8F); + center, 183, 0xFF9F8B95); super.extractRenderState(graphics, mouseX, mouseY, tickDelta); } + private void refreshMissionControls() { + if (targetKind == null || target == null) return; + targetKind.setMessage(targetKindLabel()); + targetKind.active = QuestRequestService.KILL.equals(currentAction()); + target.setHint(targetHint()); + } + + private String currentAction() { return ACTIONS[actionIndex]; } + private String currentReward() { return REWARDS[rewardIndex]; } + + private String currentTargetKind() { + if (QuestRequestService.BRING.equals(currentAction())) return QuestRequestService.ITEM; + if (QuestRequestService.PAY.equals(currentAction())) return QuestRequestService.PLAYER; + return killTargetIndex == 1 ? QuestRequestService.PLAYER : QuestRequestService.MOB; + } + private Component actionLabel() { - return Component.translatable("screen.sanctuary.quest_request.action." + ACTIONS[actionIndex]); + return Component.translatable("screen.sanctuary.quest_request.action." + currentAction()); + } + + private Component targetKindLabel() { + return Component.translatable("screen.sanctuary.quest_request.target_kind." + currentTargetKind()); + } + + private Component rewardLabel() { + return Component.translatable("screen.sanctuary.quest_request.reward_type." + currentReward()); } private Component targetHint() { - return Component.translatable("screen.sanctuary.quest_request.target_hint." + ACTIONS[actionIndex]); + return Component.translatable("screen.sanctuary.quest_request.target_hint." + + currentAction() + "." + currentTargetKind()); + } + + private Component rewardHint() { + return Component.translatable("screen.sanctuary.quest_request.reward_hint." + currentReward()); } private Values values() { - if (objective.getValue().isBlank() || target.getValue().isBlank()) return null; + if (objective.getValue().isBlank() || target.getValue().isBlank() + || rewardTarget.getValue().isBlank()) return null; try { int amount = Integer.parseInt(quantity.getValue()); - if (amount < 1 || amount > QuestRequestService.MAX_QUANTITY) return null; - return new Values(amount); + int rewardAmount = Integer.parseInt(rewardQuantity.getValue()); + if (amount < 1 || amount > QuestRequestService.MAX_QUANTITY + || rewardAmount < 1 || rewardAmount > QuestRequestService.MAX_QUANTITY) return null; + return new Values(amount, rewardAmount); } catch (NumberFormatException exception) { return null; } @@ -122,12 +189,13 @@ public final class QuestRequestEditorScreen extends Screen { Values values = values(); if (values == null || !ClientPlayNetworking.canSend(QuestRequestSubmitPayload.ID)) return; ClientPlayNetworking.send(new QuestRequestSubmitPayload(payload.pos(), alias.getValue(), - objective.getValue(), ACTIONS[actionIndex], target.getValue(), values.quantity())); + objective.getValue(), currentAction(), currentTargetKind(), target.getValue(), values.quantity(), + currentReward(), rewardTarget.getValue(), values.rewardQuantity())); onClose(); } @Override public void onClose() { if (minecraft != null) minecraft.gui.setScreen(parent); } @Override public boolean isPauseScreen() { return false; } - private record Values(int quantity) {} + private record Values(int quantity, int rewardQuantity) {} } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestEditorPayload.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestEditorPayload.java index 90216bf..ae68ea2 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestEditorPayload.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestEditorPayload.java @@ -8,7 +8,8 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload; /** Opens the bounded client editor for one server-owned quest-request sign. */ public record QuestRequestEditorPayload(BlockPos pos, String alias, String objective, - String action, String target, int quantity) implements CustomPacketPayload { + String action, String targetKind, String target, int quantity, String rewardType, + String rewardTarget, int rewardQuantity) implements CustomPacketPayload { public static final Type ID = new Type<>(SanctuaryMod.id("s2c/quest_request_editor")); public static final StreamCodec CODEC = @@ -19,12 +20,17 @@ public record QuestRequestEditorPayload(BlockPos pos, String alias, String objec alias = alias == null ? "" : alias; objective = objective == null ? "" : objective; action = action == null ? "kill" : action; + targetKind = targetKind == null ? "mob" : targetKind; target = target == null ? "" : target; quantity = Math.max(1, quantity); + rewardType = rewardType == null ? "gift" : rewardType; + rewardTarget = rewardTarget == null ? "" : rewardTarget; + rewardQuantity = Math.max(1, rewardQuantity); } private QuestRequestEditorPayload(RegistryFriendlyByteBuf buffer) { this(buffer.readBlockPos(), buffer.readUtf(32), buffer.readUtf(96), buffer.readUtf(8), + buffer.readUtf(8), buffer.readUtf(64), buffer.readVarInt(), buffer.readUtf(8), buffer.readUtf(64), buffer.readVarInt()); } @@ -33,8 +39,12 @@ public record QuestRequestEditorPayload(BlockPos pos, String alias, String objec buffer.writeUtf(alias, 32); buffer.writeUtf(objective, 96); buffer.writeUtf(action, 8); + buffer.writeUtf(targetKind, 8); buffer.writeUtf(target, 64); buffer.writeVarInt(quantity); + buffer.writeUtf(rewardType, 8); + buffer.writeUtf(rewardTarget, 64); + buffer.writeVarInt(rewardQuantity); } @Override public Type type() { return ID; } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestSubmitPayload.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestSubmitPayload.java index 8859d12..438bb26 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestSubmitPayload.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/network/QuestRequestSubmitPayload.java @@ -8,7 +8,8 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload; /** Bounded player-authored quest request; every field is revalidated by the server. */ public record QuestRequestSubmitPayload(BlockPos pos, String alias, String objective, - String action, String target, int quantity) implements CustomPacketPayload { + String action, String targetKind, String target, int quantity, String rewardType, + String rewardTarget, int rewardQuantity) implements CustomPacketPayload { public static final Type ID = new Type<>(SanctuaryMod.id("c2s/quest_request_submit")); public static final StreamCodec CODEC = @@ -16,6 +17,7 @@ public record QuestRequestSubmitPayload(BlockPos pos, String alias, String objec private QuestRequestSubmitPayload(RegistryFriendlyByteBuf buffer) { this(buffer.readBlockPos(), buffer.readUtf(32), buffer.readUtf(96), buffer.readUtf(8), + buffer.readUtf(8), buffer.readUtf(64), buffer.readVarInt(), buffer.readUtf(8), buffer.readUtf(64), buffer.readVarInt()); } @@ -24,8 +26,12 @@ public record QuestRequestSubmitPayload(BlockPos pos, String alias, String objec buffer.writeUtf(alias == null ? "" : alias, 32); buffer.writeUtf(objective == null ? "" : objective, 96); buffer.writeUtf(action == null ? "" : action, 8); + buffer.writeUtf(targetKind == null ? "" : targetKind, 8); buffer.writeUtf(target == null ? "" : target, 64); buffer.writeVarInt(quantity); + buffer.writeUtf(rewardType == null ? "" : rewardType, 8); + buffer.writeUtf(rewardTarget == null ? "" : rewardTarget, 64); + buffer.writeVarInt(rewardQuantity); } @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 6b9261b..8aa3064 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 = 19; + public static final int PROTOCOL_VERSION = 20; private static boolean initialized; private SanctuaryNetworking() { diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/registry/SanctuaryRegistries.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/registry/SanctuaryRegistries.java index debe6fd..d5d09c5 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/registry/SanctuaryRegistries.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/registry/SanctuaryRegistries.java @@ -131,13 +131,13 @@ public final class SanctuaryRegistries { StandingSignBlock standing = registerBlock( standingId, properties -> new StandingSignBlock(WoodType.OAK, properties), - BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN).mapColor(MapColor.COLOR_GREEN) + BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN).mapColor(MapColor.COLOR_PINK) .setId(blockKey(standingId)) ); WallSignBlock wall = registerBlock( wallId, properties -> new WallSignBlock(WoodType.OAK, properties), - BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_SIGN).mapColor(MapColor.COLOR_GREEN) + BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_SIGN).mapColor(MapColor.COLOR_PINK) .setId(blockKey(wallId)) ); ResourceKey itemKey = ResourceKey.create(Registries.ITEM, standingId); diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestData.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestData.java index 673ce1a..74a4721 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestData.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestData.java @@ -16,7 +16,7 @@ import net.minecraft.world.level.saveddata.SavedDataType; /** Versioned server state for player-authored quest requests attached to physical signs. */ public final class QuestRequestData extends SavedData { public static final String SAVE_KEY = "quest_requests"; - public static final int DATA_VERSION = 1; + public static final int DATA_VERSION = 2; private static final Codec CLAIM_CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.STRING.fieldOf("player").forGetter(Claim::player), @@ -33,8 +33,12 @@ public final class QuestRequestData extends SavedData { Codec.STRING.optionalFieldOf("alias", "").forGetter(Request::alias), Codec.STRING.optionalFieldOf("objective", "").forGetter(Request::objective), Codec.STRING.optionalFieldOf("action", QuestRequestService.KILL).forGetter(Request::action), + Codec.STRING.optionalFieldOf("target_kind", QuestRequestService.MOB).forGetter(Request::targetKind), Codec.STRING.optionalFieldOf("target", "").forGetter(Request::target), Codec.INT.optionalFieldOf("quantity", 1).forGetter(Request::quantity), + Codec.STRING.optionalFieldOf("reward_type", QuestRequestService.REWARD_NONE).forGetter(Request::rewardType), + Codec.STRING.optionalFieldOf("reward_target", "").forGetter(Request::rewardTarget), + Codec.INT.optionalFieldOf("reward_quantity", 0).forGetter(Request::rewardQuantity), Codec.BOOL.optionalFieldOf("configured", false).forGetter(Request::configured), CLAIM_CODEC.optionalFieldOf("claim").forGetter(Request::claim) ).apply(instance, Request::new)); @@ -112,10 +116,11 @@ public final class QuestRequestData extends SavedData { } public record Request(String id, String dimension, long pos, boolean front, String owner, - String alias, String objective, String action, String target, int quantity, boolean configured, - Optional claim) { + String alias, String objective, String action, String targetKind, String target, int quantity, + String rewardType, String rewardTarget, int rewardQuantity, boolean configured, Optional claim) { public Request { quantity = Math.max(1, quantity); + rewardQuantity = Math.max(0, rewardQuantity); claim = claim == null ? Optional.empty() : claim; } @@ -124,14 +129,16 @@ public final class QuestRequestData extends SavedData { } public Request withDetails(String newAlias, String newObjective, String newAction, - String newTarget, int newQuantity) { + String newTargetKind, String newTarget, int newQuantity, String newRewardType, + String newRewardTarget, int newRewardQuantity) { return new Request(id, dimension, pos, front, owner, newAlias, newObjective, newAction, - newTarget, newQuantity, true, Optional.empty()); + newTargetKind, newTarget, newQuantity, newRewardType, newRewardTarget, + newRewardQuantity, true, Optional.empty()); } public Request withClaim(Optional newClaim) { - return new Request(id, dimension, pos, front, owner, alias, objective, action, target, - quantity, configured, newClaim); + return new Request(id, dimension, pos, front, owner, alias, objective, action, targetKind, + target, quantity, rewardType, rewardTarget, rewardQuantity, configured, newClaim); } } } diff --git a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestService.java b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestService.java index 82bbb9a..5560791 100644 --- a/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestService.java +++ b/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestService.java @@ -1,5 +1,6 @@ package fr.koka99cab.sanctuary26.sanctuary.shop; +import com.mojang.brigadier.arguments.StringArgumentType; import fr.koka99cab.sanctuary26.sanctuary.network.QuestRequestEditorPayload; import fr.koka99cab.sanctuary26.sanctuary.network.QuestRequestSubmitPayload; import fr.koka99cab.sanctuary26.sanctuary.registry.SanctuaryRegistries; @@ -8,14 +9,18 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.UUID; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.ChatFormatting; +import net.minecraft.commands.Commands; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; @@ -34,11 +39,19 @@ public final class QuestRequestService { public static final String KILL = "kill"; public static final String BRING = "bring"; public static final String PAY = "pay"; + public static final String MOB = "mob"; + public static final String PLAYER = "player"; + public static final String ITEM = "item"; + public static final String REWARD_GIFT = "gift"; + public static final String REWARD_PAYMENT = "payment"; + public static final String REWARD_LOAN = "loan"; + public static final String REWARD_NONE = "none"; public static final int MAX_ALIAS_LENGTH = 32; public static final int MAX_OBJECTIVE_LENGTH = 96; public static final int MAX_TARGET_LENGTH = 64; public static final int MAX_QUANTITY = 9_999; private static final Set ACTIONS = Set.of(KILL, BRING, PAY); + private static final Set REWARD_TYPES = Set.of(REWARD_GIFT, REWARD_PAYMENT, REWARD_LOAN); private static final Set refreshedLayouts = new HashSet<>(); private static int tickCounter; private static boolean initialized; @@ -76,6 +89,12 @@ public final class QuestRequestService { data.requestAt(dimension(serverLevel), pos.asLong()).ifPresent(data::remove); }); ServerTickEvents.END_SERVER_TICK.register(QuestRequestService::tick); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> + dispatcher.register(Commands.literal("sanctuary_quest_accept") + .requires(source -> source.getPlayer() != null) + .then(Commands.argument("request", StringArgumentType.word()) + .executes(context -> acceptFromChat(context.getSource().getPlayer(), + StringArgumentType.getString(context, "request")))))); } /** Creates an owned draft and opens its editor after the custom sign is placed. */ @@ -90,8 +109,8 @@ public final class QuestRequestService { } QuestRequestData.Request draft = new QuestRequestData.Request( UUID.randomUUID().toString(), dimension(level), pos.asLong(), sign.isFacingFrontText(player), - player.getUUID().toString(), player.getGameProfile().name(), "", KILL, "", 1, - false, Optional.empty()); + player.getUUID().toString(), player.getGameProfile().name(), "", KILL, MOB, "", 1, + REWARD_GIFT, "", 1, false, Optional.empty()); if (!data.add(draft)) { message(player, "occupied"); return; @@ -121,9 +140,9 @@ public final class QuestRequestService { QuestRequestData.Claim claim = request.claim().orElse(null); if (claim == null) { if (request.ownerId().equals(player.getUUID())) { - inspect(player, request); + inspect(player, request, false); message(player, "owner_cannot_claim"); - } else claim(player, level, sign, data, request); + } else inspect(player, request, true); return InteractionResult.SUCCESS_SERVER; } if (claim.playerId().equals(player.getUUID()) && player.isSecondaryUseActive()) { @@ -134,10 +153,40 @@ public final class QuestRequestService { } return InteractionResult.SUCCESS_SERVER; } - inspect(player, request); + inspect(player, request, false); return InteractionResult.SUCCESS_SERVER; } + private static int acceptFromChat(ServerPlayer player, String requestId) { + QuestRequestData data = QuestRequestData.get(player.level().getServer()); + QuestRequestData.Request request = data.request(requestId).orElse(null); + if (request == null || !request.configured()) { + message(player, "missing"); + return 0; + } + if (request.ownerId().equals(player.getUUID())) { + message(player, "owner_cannot_claim"); + return 0; + } + if (request.claim().isPresent()) { + message(player, "already_claimed"); + return 0; + } + ServerLevel level = level(player.level().getServer(), request.dimension()); + BlockPos pos = BlockPos.of(request.pos()); + if (level == null || player.level() != level || player.blockPosition().distSqr(pos) > 64.0D) { + message(player, "too_far"); + return 0; + } + if (!SanctuaryRegistries.isQuestRequestSign(level.getBlockState(pos).getBlock()) + || !(level.getBlockEntity(pos) instanceof SignBlockEntity sign)) { + message(player, "missing"); + return 0; + } + claim(player, level, sign, data, request); + return 1; + } + private static void claim(ServerPlayer player, ServerLevel level, SignBlockEntity sign, QuestRequestData data, QuestRequestData.Request request) { QuestRequestData.Claim claim = new QuestRequestData.Claim(player.getUUID().toString(), @@ -151,7 +200,7 @@ public final class QuestRequestService { level.playSound(null, sign.getBlockPos(), SoundEvents.VILLAGER_YES, SoundSource.BLOCKS, 0.7F, 1.1F); player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.claimed", describeAction(claimed.action()), claimed.quantity(), describeTarget(claimed))); - inspect(player, claimed); + inspect(player, claimed, false); } /** Accepts one bounded editor submission after revalidating ownership, distance and targets. */ @@ -175,20 +224,27 @@ public final class QuestRequestService { String alias = clean(payload.alias(), MAX_ALIAS_LENGTH); String objective = clean(payload.objective(), MAX_OBJECTIVE_LENGTH); String action = clean(payload.action(), 8).toLowerCase(Locale.ROOT); + String targetKind = clean(payload.targetKind(), 8).toLowerCase(Locale.ROOT); String target = clean(payload.target(), MAX_TARGET_LENGTH); + String rewardType = clean(payload.rewardType(), 8).toLowerCase(Locale.ROOT); + String rewardTarget = clean(payload.rewardTarget(), MAX_TARGET_LENGTH); if (alias.isBlank()) alias = player.getGameProfile().name(); - if (objective.isBlank() || target.isBlank() || !ACTIONS.contains(action) - || payload.quantity() < 1 || payload.quantity() > MAX_QUANTITY) { + if (objective.isBlank() || target.isBlank() || rewardTarget.isBlank() + || !ACTIONS.contains(action) || !REWARD_TYPES.contains(rewardType) + || payload.quantity() < 1 || payload.quantity() > MAX_QUANTITY + || payload.rewardQuantity() < 1 || payload.rewardQuantity() > MAX_QUANTITY) { message(player, "invalid"); return; } - String canonicalTarget = canonicalTarget(action, target); - if (canonicalTarget == null) { + String canonicalTarget = canonicalTarget(action, targetKind, target); + String canonicalReward = canonicalItem(rewardTarget); + if (canonicalTarget == null || canonicalReward == null) { message(player, "invalid_target"); return; } QuestRequestData.Request configured = request.withDetails(alias, objective, action, - canonicalTarget, payload.quantity()); + normalizedTargetKind(action, targetKind), canonicalTarget, payload.quantity(), rewardType, + canonicalReward, payload.rewardQuantity()); if (!data.replace(configured)) { message(player, "missing"); return; @@ -197,17 +253,37 @@ public final class QuestRequestService { level.playSound(null, payload.pos(), SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.BLOCKS, 0.8F, 1.0F); message(player, "created"); - inspect(player, configured); + inspect(player, configured, false); } - private static String canonicalTarget(String action, String value) { - if (PAY.equals(action)) return clean(value, MAX_TARGET_LENGTH); - String candidate = value.indexOf(':') < 0 ? "minecraft:" + value.toLowerCase(Locale.ROOT) : value; - Identifier id = Identifier.tryParse(candidate.toLowerCase(Locale.ROOT)); - if (id == null) return null; - if (KILL.equals(action) && BuiltInRegistries.ENTITY_TYPE.getOptional(id).isEmpty()) return null; - if (BRING.equals(action) && BuiltInRegistries.ITEM.getOptional(id).isEmpty()) return null; - return id.toString(); + private static String canonicalTarget(String action, String targetKind, String value) { + if (PAY.equals(action)) return validPlayerName(value) ? value : null; + if (KILL.equals(action) && PLAYER.equals(targetKind)) return validPlayerName(value) ? value : null; + if (BRING.equals(action)) return ITEM.equals(targetKind) ? canonicalItem(value) : null; + if (!KILL.equals(action) || !MOB.equals(targetKind)) return null; + String candidate = namespaced(value); + Identifier id = Identifier.tryParse(candidate); + return id != null && BuiltInRegistries.ENTITY_TYPE.getOptional(id).isPresent() ? id.toString() : null; + } + + private static String canonicalItem(String value) { + Identifier id = Identifier.tryParse(namespaced(value)); + return id != null && BuiltInRegistries.ITEM.getOptional(id).isPresent() ? id.toString() : null; + } + + private static String namespaced(String value) { + String lowered = value.toLowerCase(Locale.ROOT); + return lowered.indexOf(':') < 0 ? "minecraft:" + lowered : lowered; + } + + private static boolean validPlayerName(String value) { + return value.matches("[A-Za-z0-9_]{1,16}"); + } + + private static String normalizedTargetKind(String action, String targetKind) { + if (BRING.equals(action)) return ITEM; + if (PAY.equals(action)) return PLAYER; + return PLAYER.equals(targetKind) ? PLAYER : MOB; } private static void openEditor(ServerPlayer player, QuestRequestData.Request request) { @@ -216,15 +292,38 @@ public final class QuestRequestService { return; } ServerPlayNetworking.send(player, new QuestRequestEditorPayload(BlockPos.of(request.pos()), - request.alias(), request.objective(), request.action(), request.target(), request.quantity())); + request.alias(), request.objective(), request.action(), request.targetKind(), request.target(), + request.quantity(), legacyRewardType(request), request.rewardTarget(), + Math.max(1, request.rewardQuantity()))); } - private static void inspect(ServerPlayer player, QuestRequestData.Request request) { - player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.details", - request.alias(), describeAction(request.action()), request.quantity(), describeTarget(request), + private static String legacyRewardType(QuestRequestData.Request request) { + return REWARD_TYPES.contains(request.rewardType()) ? request.rewardType() : REWARD_GIFT; + } + + private static void inspect(ServerPlayer player, QuestRequestData.Request request, boolean offerAcceptance) { + player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.details.header", + request.alias()).withStyle(ChatFormatting.LIGHT_PURPLE, ChatFormatting.BOLD)); + player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.details.objective", request.objective())); - request.claim().ifPresent(claim -> player.sendSystemMessage(Component.translatable( - "message.sanctuary.quest_request.claimed_by", claim.playerName()))); + player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.details.mission", + describeAction(request.action()), request.quantity(), describeTarget(request))); + if (REWARD_TYPES.contains(request.rewardType()) && !request.rewardTarget().isBlank()) { + player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.details.reward", + describeRewardType(request.rewardType()), request.rewardQuantity(), describeReward(request))); + } else player.sendSystemMessage(Component.translatable( + "message.sanctuary.quest_request.details.reward_legacy").withStyle(ChatFormatting.GRAY)); + QuestRequestData.Claim claim = request.claim().orElse(null); + if (claim != null) { + player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.claimed_by", + claim.playerName()).withStyle(ChatFormatting.GRAY)); + } else if (offerAcceptance) { + player.sendSystemMessage(Component.translatable("message.sanctuary.quest_request.accept") + .withStyle(style -> style.withColor(ChatFormatting.GREEN).withBold(true).withUnderlined(true) + .withClickEvent(new ClickEvent.RunCommand("/sanctuary_quest_accept " + request.id())) + .withHoverEvent(new HoverEvent.ShowText(Component.translatable( + "message.sanctuary.quest_request.accept_hover"))))); + } } private static Component describeAction(String action) { @@ -232,7 +331,9 @@ public final class QuestRequestService { } private static Component describeTarget(QuestRequestData.Request request) { - if (PAY.equals(request.action())) return Component.literal(request.target()); + if (PAY.equals(request.action()) || PLAYER.equals(request.targetKind())) { + return Component.literal(request.target()); + } Identifier id = Identifier.tryParse(request.target()); if (id == null) return Component.literal(request.target()); if (KILL.equals(request.action())) return BuiltInRegistries.ENTITY_TYPE.getOptional(id) @@ -241,26 +342,37 @@ public final class QuestRequestService { .map(item -> new ItemStack(item).getHoverName()).orElse(Component.literal(request.target())); } + private static Component describeRewardType(String rewardType) { + return Component.translatable("message.sanctuary.quest_request.reward." + rewardType); + } + + private static Component describeReward(QuestRequestData.Request request) { + Identifier id = Identifier.tryParse(request.rewardTarget()); + if (id == null) return Component.literal(request.rewardTarget()); + return BuiltInRegistries.ITEM.getOptional(id) + .map(item -> new ItemStack(item).getHoverName()).orElse(Component.literal(request.rewardTarget())); + } + private static void writeSign(ServerLevel level, SignBlockEntity sign, QuestRequestData.Request request) { SignText text; if (!request.configured()) { text = new SignText() .setMessage(0, Component.translatable("sign.sanctuary.quest_request.title") - .withStyle(ChatFormatting.DARK_GREEN, ChatFormatting.BOLD)) + .withStyle(ChatFormatting.LIGHT_PURPLE, ChatFormatting.BOLD)) .setMessage(1, Component.translatable("sign.sanctuary.quest_request.configure") .withStyle(ChatFormatting.GRAY)); } else { text = new SignText() - .setMessage(0, Component.literal(shortText(request.alias(), 24)) - .withStyle(ChatFormatting.DARK_GREEN, ChatFormatting.BOLD)) - .setMessage(1, Component.translatable("sign.sanctuary.quest_request.action." + request.action()) - .append(Component.literal(" ×" + request.quantity()))) - .setMessage(2, describeTarget(request).copy().withStyle(ChatFormatting.DARK_GRAY)); + .setMessage(0, Component.translatable("sign.sanctuary.quest_request.title") + .withStyle(ChatFormatting.LIGHT_PURPLE, ChatFormatting.BOLD)) + .setMessage(1, Component.literal(shortText(request.alias(), 24)) + .withStyle(ChatFormatting.DARK_PURPLE)); if (request.claim().isPresent()) { - QuestRequestData.Claim claim = request.claim().get(); - text = text.setMessage(3, Component.literal(shortText(claim.playerName(), 24)) - .withStyle(style -> style.withColor(claim.playerColor()))); - } else text = text.setMessage(3, Component.translatable("sign.sanctuary.quest_request.available") + text = text.setMessage(2, Component.translatable("sign.sanctuary.quest_request.claimed") + .withStyle(ChatFormatting.DARK_GRAY)); + } else text = text.setMessage(2, Component.translatable("sign.sanctuary.quest_request.available") + .withStyle(ChatFormatting.DARK_GRAY)); + text = text.setMessage(3, Component.translatable("sign.sanctuary.quest_request.details") .withStyle(ChatFormatting.GRAY)); } sign.setText(text, request.front()); diff --git a/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_sign.json b/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_sign.json index 237fbd0..42d20f3 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_sign.json +++ b/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_sign.json @@ -1,20 +1,20 @@ { "variants": { - "rotation=0": { "model": "sanctuary:block/emerald_quest_sign_rot_0" }, - "rotation=1": { "model": "sanctuary:block/emerald_quest_sign_rot_1" }, - "rotation=2": { "model": "sanctuary:block/emerald_quest_sign_rot_2" }, - "rotation=3": { "model": "sanctuary:block/emerald_quest_sign_rot_3" }, - "rotation=4": { "model": "sanctuary:block/emerald_quest_sign_rot_0", "y": 90 }, - "rotation=5": { "model": "sanctuary:block/emerald_quest_sign_rot_1", "y": 90 }, - "rotation=6": { "model": "sanctuary:block/emerald_quest_sign_rot_2", "y": 90 }, - "rotation=7": { "model": "sanctuary:block/emerald_quest_sign_rot_3", "y": 90 }, - "rotation=8": { "model": "sanctuary:block/emerald_quest_sign_rot_0", "y": 180 }, - "rotation=9": { "model": "sanctuary:block/emerald_quest_sign_rot_1", "y": 180 }, - "rotation=10": { "model": "sanctuary:block/emerald_quest_sign_rot_2", "y": 180 }, - "rotation=11": { "model": "sanctuary:block/emerald_quest_sign_rot_3", "y": 180 }, - "rotation=12": { "model": "sanctuary:block/emerald_quest_sign_rot_0", "y": 270 }, - "rotation=13": { "model": "sanctuary:block/emerald_quest_sign_rot_1", "y": 270 }, - "rotation=14": { "model": "sanctuary:block/emerald_quest_sign_rot_2", "y": 270 }, - "rotation=15": { "model": "sanctuary:block/emerald_quest_sign_rot_3", "y": 270 } + "rotation=0": { "model": "sanctuary:block/quest_request_sign_rot_0" }, + "rotation=1": { "model": "sanctuary:block/quest_request_sign_rot_1" }, + "rotation=2": { "model": "sanctuary:block/quest_request_sign_rot_2" }, + "rotation=3": { "model": "sanctuary:block/quest_request_sign_rot_3" }, + "rotation=4": { "model": "sanctuary:block/quest_request_sign_rot_0", "y": 90 }, + "rotation=5": { "model": "sanctuary:block/quest_request_sign_rot_1", "y": 90 }, + "rotation=6": { "model": "sanctuary:block/quest_request_sign_rot_2", "y": 90 }, + "rotation=7": { "model": "sanctuary:block/quest_request_sign_rot_3", "y": 90 }, + "rotation=8": { "model": "sanctuary:block/quest_request_sign_rot_0", "y": 180 }, + "rotation=9": { "model": "sanctuary:block/quest_request_sign_rot_1", "y": 180 }, + "rotation=10": { "model": "sanctuary:block/quest_request_sign_rot_2", "y": 180 }, + "rotation=11": { "model": "sanctuary:block/quest_request_sign_rot_3", "y": 180 }, + "rotation=12": { "model": "sanctuary:block/quest_request_sign_rot_0", "y": 270 }, + "rotation=13": { "model": "sanctuary:block/quest_request_sign_rot_1", "y": 270 }, + "rotation=14": { "model": "sanctuary:block/quest_request_sign_rot_2", "y": 270 }, + "rotation=15": { "model": "sanctuary:block/quest_request_sign_rot_3", "y": 270 } } } diff --git a/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_wall_sign.json b/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_wall_sign.json index cf9f08f..dc63c48 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_wall_sign.json +++ b/sanctuary/src/main/resources/assets/sanctuary/blockstates/quest_request_wall_sign.json @@ -1,8 +1,8 @@ { "variants": { - "facing=east": { "model": "sanctuary:block/emerald_quest_wall_sign", "y": 270 }, - "facing=north": { "model": "sanctuary:block/emerald_quest_wall_sign", "y": 180 }, - "facing=south": { "model": "sanctuary:block/emerald_quest_wall_sign" }, - "facing=west": { "model": "sanctuary:block/emerald_quest_wall_sign", "y": 90 } + "facing=east": { "model": "sanctuary:block/quest_request_wall_sign", "y": 270 }, + "facing=north": { "model": "sanctuary:block/quest_request_wall_sign", "y": 180 }, + "facing=south": { "model": "sanctuary:block/quest_request_wall_sign" }, + "facing=west": { "model": "sanctuary:block/quest_request_wall_sign", "y": 90 } } } diff --git a/sanctuary/src/main/resources/assets/sanctuary/items/quest_request_sign.json b/sanctuary/src/main/resources/assets/sanctuary/items/quest_request_sign.json index a8e35a8..6b3aeda 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/items/quest_request_sign.json +++ b/sanctuary/src/main/resources/assets/sanctuary/items/quest_request_sign.json @@ -1,6 +1,6 @@ { "model": { "type": "minecraft:model", - "model": "sanctuary:item/emerald_quest_sign" + "model": "sanctuary:item/quest_request_sign" } } diff --git a/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json b/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json index 30c63f5..86f0e43 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json +++ b/sanctuary/src/main/resources/assets/sanctuary/lang/en_us.json @@ -14,16 +14,30 @@ "screen.sanctuary.quest_request.objective": "Objective", "screen.sanctuary.quest_request.objective_hint": "Briefly describe the request", "screen.sanctuary.quest_request.action": "Type", + "screen.sanctuary.quest_request.mission": "Mission: type · target · quantity", "screen.sanctuary.quest_request.action.kill": "KILL", "screen.sanctuary.quest_request.action.bring": "BRING", "screen.sanctuary.quest_request.action.pay": "PAY", "screen.sanctuary.quest_request.quantity": "Quantity", "screen.sanctuary.quest_request.target": "Target", - "screen.sanctuary.quest_request.target_hint.kill": "minecraft:zombie", - "screen.sanctuary.quest_request.target_hint.bring": "minecraft:wheat", - "screen.sanctuary.quest_request.target_hint.pay": "Player name", + "screen.sanctuary.quest_request.target_kind.mob": "MOB", + "screen.sanctuary.quest_request.target_kind.player": "PLAYER", + "screen.sanctuary.quest_request.target_kind.item": "ITEM", + "screen.sanctuary.quest_request.target_hint.kill.mob": "minecraft:zombie", + "screen.sanctuary.quest_request.target_hint.kill.player": "Player name to eliminate", + "screen.sanctuary.quest_request.target_hint.bring.item": "minecraft:wheat", + "screen.sanctuary.quest_request.target_hint.pay.player": "Player name to pay", + "screen.sanctuary.quest_request.reward": "Promised reward: form · quantity · item", + "screen.sanctuary.quest_request.reward_type.gift": "GIFT", + "screen.sanctuary.quest_request.reward_type.payment": "PAYMENT", + "screen.sanctuary.quest_request.reward_type.loan": "LOAN", + "screen.sanctuary.quest_request.reward_quantity": "Reward quantity", + "screen.sanctuary.quest_request.reward_target": "Reward item", + "screen.sanctuary.quest_request.reward_hint.gift": "minecraft:diamond", + "screen.sanctuary.quest_request.reward_hint.payment": "anotherworld:ruby", + "screen.sanctuary.quest_request.reward_hint.loan": "minecraft:elytra", "screen.sanctuary.quest_request.publish": "Publish", - "screen.sanctuary.quest_request.help": "Right click: take · Shift + right click: abandon", + "screen.sanctuary.quest_request.help": "Right click: details · then accept in chat", "message.sanctuary.black_market.banned": "You are banned from the Black Market.", "message.sanctuary.black_market.protected_sign": "Only the seller or an operator can remove this listing sign.", "message.sanctuary.black_market.modified": "Listing %s updated.", @@ -68,17 +82,27 @@ "message.sanctuary.quest_request.already_claimed": "This request was just taken.", "message.sanctuary.quest_request.claimed": "Request taken: %s · %s × %s.", "message.sanctuary.quest_request.details": "%s requests: %s · %s × %s · %s", + "message.sanctuary.quest_request.details.header": "— Request from %s —", + "message.sanctuary.quest_request.details.objective": "Objective: %s", + "message.sanctuary.quest_request.details.mission": "Mission: %s · %s × %s", + "message.sanctuary.quest_request.details.reward": "Promised reward: %s · %s × %s", + "message.sanctuary.quest_request.details.reward_legacy": "No reward was specified on this legacy request.", + "message.sanctuary.quest_request.accept": "[ACCEPT QUEST]", + "message.sanctuary.quest_request.accept_hover": "Click to confirm after reading every detail.", "message.sanctuary.quest_request.claimed_by": "Taken by %s.", "message.sanctuary.quest_request.too_far": "You are too far from the request sign.", "message.sanctuary.quest_request.missing": "This request sign no longer exists.", "message.sanctuary.quest_request.not_owner": "Only the requester can edit this available sign.", "message.sanctuary.quest_request.invalid": "The request contains an empty or out-of-range value.", - "message.sanctuary.quest_request.invalid_target": "Unknown target: use a valid mob/item ID, or a player name for PAY.", + "message.sanctuary.quest_request.invalid_target": "Unknown target: use a valid mob/item ID or a valid player name.", "message.sanctuary.quest_request.client_required": "An up-to-date Sanctuary 26.2 client is required to configure this sign.", "message.sanctuary.quest_request.created": "Quest request published.", "message.sanctuary.quest_request.action.kill": "Kill", "message.sanctuary.quest_request.action.bring": "Bring", "message.sanctuary.quest_request.action.pay": "Pay", + "message.sanctuary.quest_request.reward.gift": "Permanent gift", + "message.sanctuary.quest_request.reward.payment": "Payment", + "message.sanctuary.quest_request.reward.loan": "Loan to be returned", "mod.sanctuary.description": "Owns progression and gives meaning to the adventure.", "screen.sanctuary.atlas": "Atlas", "screen.sanctuary.atlas.chunk_count": "Visited chunks: %s (%s visible)", @@ -397,7 +421,9 @@ "sign.sanctuary.quest_request.action.kill": "KILL", "sign.sanctuary.quest_request.action.bring": "BRING", "sign.sanctuary.quest_request.action.pay": "PAY", - "sign.sanctuary.quest_request.available": "Right click", + "sign.sanctuary.quest_request.available": "AVAILABLE", + "sign.sanctuary.quest_request.claimed": "TAKEN", + "sign.sanctuary.quest_request.details": "Click: details", "message.sanctuary.companion.active_locked": "Unlock the active companion power in Progression for %s XP levels.", "message.sanctuary.shop.delivery_box_registered": "Mailbox registered at %s, %s, %s.", "message.sanctuary.shop.delivery_box_not_owner": "This Mailbox belongs to another player.", diff --git a/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json b/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json index 54db649..0654486 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json +++ b/sanctuary/src/main/resources/assets/sanctuary/lang/fr_fr.json @@ -14,16 +14,30 @@ "screen.sanctuary.quest_request.objective": "Objectif", "screen.sanctuary.quest_request.objective_hint": "Décris brièvement la demande", "screen.sanctuary.quest_request.action": "Type", + "screen.sanctuary.quest_request.mission": "Mission : type · cible · quantité", "screen.sanctuary.quest_request.action.kill": "KILL", "screen.sanctuary.quest_request.action.bring": "BRING", "screen.sanctuary.quest_request.action.pay": "PAY", "screen.sanctuary.quest_request.quantity": "Quantité", "screen.sanctuary.quest_request.target": "Cible", - "screen.sanctuary.quest_request.target_hint.kill": "minecraft:zombie", - "screen.sanctuary.quest_request.target_hint.bring": "minecraft:wheat", - "screen.sanctuary.quest_request.target_hint.pay": "Pseudonyme du joueur", + "screen.sanctuary.quest_request.target_kind.mob": "MOB", + "screen.sanctuary.quest_request.target_kind.player": "JOUEUR", + "screen.sanctuary.quest_request.target_kind.item": "ITEM", + "screen.sanctuary.quest_request.target_hint.kill.mob": "minecraft:zombie", + "screen.sanctuary.quest_request.target_hint.kill.player": "Pseudonyme à éliminer", + "screen.sanctuary.quest_request.target_hint.bring.item": "minecraft:wheat", + "screen.sanctuary.quest_request.target_hint.pay.player": "Pseudonyme à payer", + "screen.sanctuary.quest_request.reward": "Récompense promise : forme · quantité · item", + "screen.sanctuary.quest_request.reward_type.gift": "DON", + "screen.sanctuary.quest_request.reward_type.payment": "PAIEMENT", + "screen.sanctuary.quest_request.reward_type.loan": "PRÊT", + "screen.sanctuary.quest_request.reward_quantity": "Quantité de la récompense", + "screen.sanctuary.quest_request.reward_target": "Item de la récompense", + "screen.sanctuary.quest_request.reward_hint.gift": "minecraft:diamond", + "screen.sanctuary.quest_request.reward_hint.payment": "anotherworld:ruby", + "screen.sanctuary.quest_request.reward_hint.loan": "minecraft:elytra", "screen.sanctuary.quest_request.publish": "Publier", - "screen.sanctuary.quest_request.help": "Clic droit : prendre · Maj + clic droit : abandonner", + "screen.sanctuary.quest_request.help": "Clic droit : détails · accepter ensuite dans le chat", "message.sanctuary.black_market.banned": "Vous êtes banni du Black Market.", "message.sanctuary.black_market.protected_sign": "Seuls le vendeur et les opérateurs peuvent retirer ce panneau d'annonce.", "message.sanctuary.black_market.modified": "Annonce %s modifiée.", @@ -68,17 +82,27 @@ "message.sanctuary.quest_request.already_claimed": "Cette demande vient d'être prise.", "message.sanctuary.quest_request.claimed": "Demande prise : %s · %s × %s.", "message.sanctuary.quest_request.details": "%s demande : %s · %s × %s · %s", + "message.sanctuary.quest_request.details.header": "— Demande de %s —", + "message.sanctuary.quest_request.details.objective": "Objectif : %s", + "message.sanctuary.quest_request.details.mission": "Mission : %s · %s × %s", + "message.sanctuary.quest_request.details.reward": "Récompense promise : %s · %s × %s", + "message.sanctuary.quest_request.details.reward_legacy": "Aucune récompense indiquée sur cette ancienne demande.", + "message.sanctuary.quest_request.accept": "[ACCEPTER LA QUÊTE]", + "message.sanctuary.quest_request.accept_hover": "Clique pour confirmer après avoir lu tous les détails.", "message.sanctuary.quest_request.claimed_by": "Prise par %s.", "message.sanctuary.quest_request.too_far": "Tu es trop loin du panneau de demande.", "message.sanctuary.quest_request.missing": "Ce panneau de demande n'existe plus.", "message.sanctuary.quest_request.not_owner": "Seul le demandeur peut modifier ce panneau libre.", "message.sanctuary.quest_request.invalid": "La demande contient une valeur vide ou hors limites.", - "message.sanctuary.quest_request.invalid_target": "Cible inconnue : utilise un ID de mob/item valide, ou un pseudonyme pour PAY.", + "message.sanctuary.quest_request.invalid_target": "Cible inconnue : utilise un ID de mob/item valide ou un pseudonyme de joueur valide.", "message.sanctuary.quest_request.client_required": "Le client Sanctuary 26.2 à jour est requis pour configurer ce panneau.", "message.sanctuary.quest_request.created": "Demande de quête publiée.", "message.sanctuary.quest_request.action.kill": "Tuer", "message.sanctuary.quest_request.action.bring": "Ramener", "message.sanctuary.quest_request.action.pay": "Payer", + "message.sanctuary.quest_request.reward.gift": "Don définitif", + "message.sanctuary.quest_request.reward.payment": "Paiement", + "message.sanctuary.quest_request.reward.loan": "Prêt à restituer", "mod.sanctuary.description": "Possède la progression et donne un sens à l'aventure.", "screen.sanctuary.atlas": "Atlas", "screen.sanctuary.atlas.chunk_count": "Chunks visités : %s (%s visibles)", @@ -397,7 +421,9 @@ "sign.sanctuary.quest_request.action.kill": "TUER", "sign.sanctuary.quest_request.action.bring": "RAMENER", "sign.sanctuary.quest_request.action.pay": "PAYER", - "sign.sanctuary.quest_request.available": "Clic droit", + "sign.sanctuary.quest_request.available": "DISPONIBLE", + "sign.sanctuary.quest_request.claimed": "PRISE", + "sign.sanctuary.quest_request.details": "Clic : détails", "message.sanctuary.companion.active_locked": "Débloquez le pouvoir actif du familier dans Progression pour %s niveaux d'XP.", "message.sanctuary.shop.delivery_box_registered": "Boîte aux lettres enregistrée en %s, %s, %s.", "message.sanctuary.shop.delivery_box_not_owner": "Cette boîte aux lettres appartient à un autre joueur.", diff --git a/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json b/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json index cc29938..06df664 100644 --- a/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json +++ b/sanctuary/src/main/resources/assets/sanctuary/lang/ru_ru.json @@ -7,16 +7,30 @@ "screen.sanctuary.quest_request.objective": "Цель", "screen.sanctuary.quest_request.objective_hint": "Кратко опишите запрос", "screen.sanctuary.quest_request.action": "Тип", + "screen.sanctuary.quest_request.mission": "Задание: тип · цель · количество", "screen.sanctuary.quest_request.action.kill": "УБИТЬ", "screen.sanctuary.quest_request.action.bring": "ПРИНЕСТИ", "screen.sanctuary.quest_request.action.pay": "ЗАПЛАТИТЬ", "screen.sanctuary.quest_request.quantity": "Количество", "screen.sanctuary.quest_request.target": "Цель", - "screen.sanctuary.quest_request.target_hint.kill": "minecraft:zombie", - "screen.sanctuary.quest_request.target_hint.bring": "minecraft:wheat", - "screen.sanctuary.quest_request.target_hint.pay": "Имя игрока", + "screen.sanctuary.quest_request.target_kind.mob": "МОБ", + "screen.sanctuary.quest_request.target_kind.player": "ИГРОК", + "screen.sanctuary.quest_request.target_kind.item": "ПРЕДМЕТ", + "screen.sanctuary.quest_request.target_hint.kill.mob": "minecraft:zombie", + "screen.sanctuary.quest_request.target_hint.kill.player": "Имя игрока-цели", + "screen.sanctuary.quest_request.target_hint.bring.item": "minecraft:wheat", + "screen.sanctuary.quest_request.target_hint.pay.player": "Имя получателя", + "screen.sanctuary.quest_request.reward": "Обещанная награда: вид · количество · предмет", + "screen.sanctuary.quest_request.reward_type.gift": "ПОДАРОК", + "screen.sanctuary.quest_request.reward_type.payment": "ОПЛАТА", + "screen.sanctuary.quest_request.reward_type.loan": "ВЗАЙМЫ", + "screen.sanctuary.quest_request.reward_quantity": "Количество награды", + "screen.sanctuary.quest_request.reward_target": "Предмет награды", + "screen.sanctuary.quest_request.reward_hint.gift": "minecraft:diamond", + "screen.sanctuary.quest_request.reward_hint.payment": "anotherworld:ruby", + "screen.sanctuary.quest_request.reward_hint.loan": "minecraft:elytra", "screen.sanctuary.quest_request.publish": "Опубликовать", - "screen.sanctuary.quest_request.help": "ПКМ: взять · Shift + ПКМ: отказаться", + "screen.sanctuary.quest_request.help": "ПКМ: подробности · затем принять в чате", "screen.sanctuary.atlas": "Атлас", "screen.sanctuary.atlas.chunk_count": "Посещённые чанки: %s (видно: %s)", "screen.sanctuary.atlas.marker_count": "Видимые этапы: %s", @@ -334,7 +348,9 @@ "sign.sanctuary.quest_request.action.kill": "УБИТЬ", "sign.sanctuary.quest_request.action.bring": "ПРИНЕСТИ", "sign.sanctuary.quest_request.action.pay": "ПЛАТИТЬ", - "sign.sanctuary.quest_request.available": "ПКМ", + "sign.sanctuary.quest_request.available": "СВОБОДНО", + "sign.sanctuary.quest_request.claimed": "ЗАНЯТО", + "sign.sanctuary.quest_request.details": "ПКМ: детали", "message.sanctuary.quest_request.occupied": "На этой табличке уже есть запрос.", "message.sanctuary.quest_request.protected": "Только заказчик или оператор может забрать эту табличку.", "message.sanctuary.quest_request.configuring": "Заказчик ещё настраивает это задание.", @@ -343,17 +359,27 @@ "message.sanctuary.quest_request.already_claimed": "Этот запрос только что взяли.", "message.sanctuary.quest_request.claimed": "Запрос взят: %s · %s × %s.", "message.sanctuary.quest_request.details": "%s просит: %s · %s × %s · %s", + "message.sanctuary.quest_request.details.header": "— Запрос от %s —", + "message.sanctuary.quest_request.details.objective": "Цель: %s", + "message.sanctuary.quest_request.details.mission": "Задание: %s · %s × %s", + "message.sanctuary.quest_request.details.reward": "Обещанная награда: %s · %s × %s", + "message.sanctuary.quest_request.details.reward_legacy": "В этом старом запросе награда не указана.", + "message.sanctuary.quest_request.accept": "[ПРИНЯТЬ ЗАДАНИЕ]", + "message.sanctuary.quest_request.accept_hover": "Нажмите, чтобы подтвердить после чтения всех подробностей.", "message.sanctuary.quest_request.claimed_by": "Взял: %s.", "message.sanctuary.quest_request.too_far": "Вы слишком далеко от таблички запроса.", "message.sanctuary.quest_request.missing": "Этой таблички запроса больше нет.", "message.sanctuary.quest_request.not_owner": "Только заказчик может изменять свободную табличку.", "message.sanctuary.quest_request.invalid": "В запросе есть пустое или недопустимое значение.", - "message.sanctuary.quest_request.invalid_target": "Неизвестная цель: укажите ID моба/предмета или имя игрока для PAY.", + "message.sanctuary.quest_request.invalid_target": "Неизвестная цель: укажите ID моба/предмета или допустимое имя игрока.", "message.sanctuary.quest_request.client_required": "Для настройки нужен актуальный клиент Sanctuary 26.2.", "message.sanctuary.quest_request.created": "Запрос задания опубликован.", "message.sanctuary.quest_request.action.kill": "Убить", "message.sanctuary.quest_request.action.bring": "Принести", "message.sanctuary.quest_request.action.pay": "Заплатить", + "message.sanctuary.quest_request.reward.gift": "Подарок навсегда", + "message.sanctuary.quest_request.reward.payment": "Оплата", + "message.sanctuary.quest_request.reward.loan": "Взаймы с возвратом", "message.sanctuary.companion.active_locked": "Откройте активную силу спутника в Прогрессе за %s уровней опыта.", "message.sanctuary.shop.delivery_box_registered": "Почтовый ящик зарегистрирован: %s, %s, %s.", "message.sanctuary.shop.delivery_box_not_owner": "Этот почтовый ящик принадлежит другому игроку.", diff --git a/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_0.json b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_0.json new file mode 100644 index 0000000..9cbb4c5 --- /dev/null +++ b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_0.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/template_sign_rot_0", + "textures": { + "all": "sanctuary:block/pink_quest_request_sign", + "particle": "minecraft:block/oak_planks" + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_1.json b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_1.json new file mode 100644 index 0000000..f758399 --- /dev/null +++ b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_1.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/template_sign_rot_1", + "textures": { + "all": "sanctuary:block/pink_quest_request_sign", + "particle": "minecraft:block/oak_planks" + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_2.json b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_2.json new file mode 100644 index 0000000..f0f7a64 --- /dev/null +++ b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_2.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/template_sign_rot_2", + "textures": { + "all": "sanctuary:block/pink_quest_request_sign", + "particle": "minecraft:block/oak_planks" + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_3.json b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_3.json new file mode 100644 index 0000000..7535b36 --- /dev/null +++ b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_sign_rot_3.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/template_sign_rot_3", + "textures": { + "all": "sanctuary:block/pink_quest_request_sign", + "particle": "minecraft:block/oak_planks" + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_wall_sign.json b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_wall_sign.json new file mode 100644 index 0000000..ab0f60e --- /dev/null +++ b/sanctuary/src/main/resources/assets/sanctuary/models/block/quest_request_wall_sign.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/template_wall_sign", + "textures": { + "all": "sanctuary:block/pink_quest_request_sign", + "particle": "minecraft:block/oak_planks" + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/models/item/quest_request_sign.json b/sanctuary/src/main/resources/assets/sanctuary/models/item/quest_request_sign.json new file mode 100644 index 0000000..5c72865 --- /dev/null +++ b/sanctuary/src/main/resources/assets/sanctuary/models/item/quest_request_sign.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "sanctuary:item/pink_quest_request_sign" + } +} diff --git a/sanctuary/src/main/resources/assets/sanctuary/textures/block/pink_quest_request_sign.png b/sanctuary/src/main/resources/assets/sanctuary/textures/block/pink_quest_request_sign.png new file mode 100644 index 0000000..04632aa Binary files /dev/null and b/sanctuary/src/main/resources/assets/sanctuary/textures/block/pink_quest_request_sign.png differ diff --git a/sanctuary/src/main/resources/assets/sanctuary/textures/item/pink_quest_request_sign.png b/sanctuary/src/main/resources/assets/sanctuary/textures/item/pink_quest_request_sign.png new file mode 100644 index 0000000..b8983c3 Binary files /dev/null and b/sanctuary/src/main/resources/assets/sanctuary/textures/item/pink_quest_request_sign.png differ diff --git a/sanctuary/src/main/resources/fabric.mod.json b/sanctuary/src/main/resources/fabric.mod.json index 45e232d..f357148 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": 19, + "network_protocol": 20, "lifecycle": "${lifecycle}", "pack_version": "${pack_version}", "descriptions": { diff --git a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestModelSmoke.java b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestModelSmoke.java index 662a506..189249e 100644 --- a/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestModelSmoke.java +++ b/sanctuary/src/test/java/fr/koka99cab/sanctuary26/sanctuary/shop/QuestRequestModelSmoke.java @@ -11,14 +11,21 @@ public final class QuestRequestModelSmoke { UUID owner = UUID.fromString("6c43c35a-3498-41bb-a77e-10c679e635d1"); UUID claimant = UUID.fromString("8ff11ade-a604-4d77-a770-d487684367a1"); QuestRequestData.Request draft = new QuestRequestData.Request("request", "minecraft:overworld", - 42L, true, owner.toString(), "Koka", "", QuestRequestService.KILL, "", 1, + 42L, true, owner.toString(), "Koka", "", QuestRequestService.KILL, + QuestRequestService.MOB, "", 1, QuestRequestService.REWARD_GIFT, "", 1, false, Optional.empty()); QuestRequestData data = new QuestRequestData(); require(data.add(draft), "Request draft creation failed"); require(!data.add(draft), "Duplicate request position accepted"); QuestRequestData.Request configured = draft.withDetails("Koka", "Nettoyer les ruines", - QuestRequestService.KILL, "minecraft:zombie", 20); + QuestRequestService.KILL, QuestRequestService.PLAYER, "Hunter", 1, + QuestRequestService.REWARD_LOAN, "minecraft:elytra", 1); require(data.replace(configured), "Request configuration failed"); + require(configured.targetKind().equals(QuestRequestService.PLAYER), + "Player kill targets must be preserved"); + require(configured.rewardType().equals(QuestRequestService.REWARD_LOAN) + && configured.rewardTarget().equals("minecraft:elytra"), + "Loan rewards must be preserved"); QuestRequestData.Claim claim = new QuestRequestData.Claim(claimant.toString(), "Hunter", 0x44AAFF, 10L); QuestRequestData.Request claimed = configured.withClaim(Optional.of(claim));