Sanctuary 26.2.0-alpha.199 — contrats complets des demandes de quêtes #6

Merged
koka merged 4 commits from release/26.2.0-alpha.199 into main 2026-08-27 05:58:21 +00:00
33 changed files with 683 additions and 175 deletions
+15
View File
@@ -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 lalpha.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 à linvitation à 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 labsence 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 dun item et dune quantité validés côté serveur ;
- présente séparément dans le chat le demandeur, lobjectif, 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 dun 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.
+4 -2
View File
@@ -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.
Lalpha.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 lobjectif complet, la cible mob/item/joueur et la récompense promise sous forme de don, paiement ou prêt ; la quête nest prise quaprè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.
Lalpha.198 borne lautomatisation du tonneau de fermentation : chaque emplacement naccepte quune 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 linventaire suit le comportement vanilla sur toutes les rangées débloquées, y compris les rangées 5 et 6, tandis quune rangée de stockage ou doverflow nalimente que la hotbar.
@@ -34,7 +36,7 @@ Lalpha.195 charge le catalogue Shop depuis le CSV rééquilibré et conserve
Lalpha.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, lolivier 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 quune chance rare de semence sur la végétation permet de continuer un monde existant. Aucune réinitialisation de sauvegarde nest requise.
Le [wiki cuisine de lalpha.197](itsalive/FOOD_WIKI.md) détaille les cultures, les six postes, lautomatisation, les ingrédients transformés, le livre et ses 64 recettes.
Le [wiki cuisine de lalpha.199](itsalive/FOOD_WIKI.md) détaille les cultures, les six postes, lautomatisation, les ingrédients transformés, le livre et ses 64 recettes.
Lalpha.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 dentré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 lordre pour 2, 4, 8, 12, 18 puis 25 niveaux. Laide de boîte aux lettres remonte légèrement et la bourse rubis/saphirs sancre en bas à droite.
+71 -25
View File
@@ -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")
+2 -2
View File
@@ -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
@@ -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
}
}
+11 -11
View File
@@ -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"
+2 -2
View File
@@ -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"
+1 -1
View File
@@ -3,7 +3,7 @@
"game": "minecraft",
"name": "Sanctuary 26.2",
"summary": "Sanctuary 26.2 — progression, exploration, worldgen and multiplayer systems by KOKA99CAB.",
"versionId": "26.2.0-alpha.198",
"versionId": "26.2.0-alpha.199",
"files": [
{
"path": "mods/fabric-api-0.158.0+26.2.jar",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"pack_version": "26.2.0-alpha.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,
+80 -10
View File
@@ -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")
}
@@ -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) {}
}
@@ -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<QuestRequestEditorPayload> ID =
new Type<>(SanctuaryMod.id("s2c/quest_request_editor"));
public static final StreamCodec<RegistryFriendlyByteBuf, QuestRequestEditorPayload> 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<? extends CustomPacketPayload> type() { return ID; }
@@ -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<QuestRequestSubmitPayload> ID =
new Type<>(SanctuaryMod.id("c2s/quest_request_submit"));
public static final StreamCodec<RegistryFriendlyByteBuf, QuestRequestSubmitPayload> 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<? extends CustomPacketPayload> type() { return ID; }
@@ -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() {
@@ -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<Item> itemKey = ResourceKey.create(Registries.ITEM, standingId);
@@ -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> 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> claim) {
String alias, String objective, String action, String targetKind, String target, int quantity,
String rewardType, String rewardTarget, int rewardQuantity, boolean configured, Optional<Claim> 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<Claim> 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);
}
}
}
@@ -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<String> ACTIONS = Set.of(KILL, BRING, PAY);
private static final Set<String> REWARD_TYPES = Set.of(REWARD_GIFT, REWARD_PAYMENT, REWARD_LOAN);
private static final Set<String> 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());
@@ -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 }
}
}
@@ -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 }
}
}
@@ -1,6 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "sanctuary:item/emerald_quest_sign"
"model": "sanctuary:item/quest_request_sign"
}
}
@@ -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.",
@@ -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.",
@@ -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": "Этот почтовый ящик принадлежит другому игроку.",
@@ -0,0 +1,7 @@
{
"parent": "minecraft:block/template_sign_rot_0",
"textures": {
"all": "sanctuary:block/pink_quest_request_sign",
"particle": "minecraft:block/oak_planks"
}
}
@@ -0,0 +1,7 @@
{
"parent": "minecraft:block/template_sign_rot_1",
"textures": {
"all": "sanctuary:block/pink_quest_request_sign",
"particle": "minecraft:block/oak_planks"
}
}
@@ -0,0 +1,7 @@
{
"parent": "minecraft:block/template_sign_rot_2",
"textures": {
"all": "sanctuary:block/pink_quest_request_sign",
"particle": "minecraft:block/oak_planks"
}
}
@@ -0,0 +1,7 @@
{
"parent": "minecraft:block/template_sign_rot_3",
"textures": {
"all": "sanctuary:block/pink_quest_request_sign",
"particle": "minecraft:block/oak_planks"
}
}
@@ -0,0 +1,7 @@
{
"parent": "minecraft:block/template_wall_sign",
"textures": {
"all": "sanctuary:block/pink_quest_request_sign",
"particle": "minecraft:block/oak_planks"
}
}
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "sanctuary:item/pink_quest_request_sign"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

+1 -1
View File
@@ -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": {
@@ -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));